source('~/Dropbox/PoldrackLab/SRO_DDM_Analyses/code/workspace_scripts/ez_fa_data.R')
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Dropping 0 variables with correlations above 0.85
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Using transformation:log
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
No positively skewed variables found.* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
No negatively skewed variables found.* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Dropping 0 variables with correlations above 0.85
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Using transformation:log
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
No positively skewed variables found.* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
No negatively skewed variables found.* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Warning: Unknown columns: `X`, `sub_id`, `subj_id`
Warning: Unknown columns: `X`, `sub_id`, `subj_id`
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Dropping 0 variables with correlations above 0.85
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Using transformation:log
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
No positively skewed variables found.* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
No negatively skewed variables found.* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Warning: Unknown columns: `X`, `sub_id`, `subj_id`
fig_path = '/Users/zeynepenkavi/Dropbox/PoldrackLab/SRO_DDM_Analyses/output/figures/'
source('~/Dropbox/PoldrackLab/SRO_Retest_Analyses/code/figure_scripts/figure_res_wrapper.R')
source('~/Dropbox/PoldrackLab/SRO_Retest_Analyses/code/helper_functions/make_rel_df.R')
To reduce the number of variables in a data-driven way (instead of just selecting the variables that went in to the ontology paper) I’ll apply the cleaning methods from the ontology pipeline: - dropping variables with r>0.85. - Remove outliers (>2.5 SD away) - transformation of non-normal variables (should be particularly useful for a set of variables with many response times) - Residualize Age and Sex effects
res_clean_test_data_ez_522_condition = res_clean_test_data_ez_522 %>%
select((measure_labels %>%
filter(raw_fit == "EZ" & overall_difference == "condition"))$dv)
PCA on EZ variables of test data
Number of variables analysis
In this dataset there are more variables than observations.
Given the large number of measures some can possible be represented as linear combinations as others.
This would result in 0 or negative values in the eigenvalues of the correlation matrix and would mean that the correlation matrix is not positive definite.
It is hard to detect such multiple dependencies. Given the number of possible combinations of variables from all possible variables it is also impossible to compute the exact largest combination of variables that has a positive definite correlation matrix.
So instead I used a sampling approach. Below is a plot of the proportion of positive definite matrices out of 1000 samples of the given numbers of variables drawn from all variables.
The plot shows that there is a steady decrease in the proportion of positive definite matrices the more variables are used. It sharply drops to 0 from chance at 160 variables.
max_num_vars_summary = read.csv('/Users/zeynepenkavi/Dropbox/PoldrackLab/SRO_DDM_Analyses/input/max_num_vars_summary.csv')
p = max_num_vars_summary %>%
ggplot(aes(as.factor(cur_num_vars), prop_det_pos))+
geom_point()+
geom_line(group=1)+
xlab("Number of variables drawn")+
ylab("Proportion of positive definite correlation matrices")+
theme(axis.text.x = element_text(angle = 90))
ggsave('max_num_vars.jpeg',plot=p, device = 'jpeg', path = fig_path, width =12, height = 4, units = "in")
p

This means that any effort to reduce dimensionality using an eigenvalue decomposition will encounter problems.
For numerical efficiency and stability many PCA applications use singular value decomposition instead (e.g. see documentation for the princomp and prcomp functions in the base package).
To be consistent with the dimensionality reduction methods used in the ontology paper I will use the psych package functions for PCA and EFA. These rely on an eigenvalue decomposition but have a built-in function to smooth over negative eigenvalues that prints a warning but continues to execute and yield a solution.
But to confirm that the resulting factors are not problematic I will run an SVD and recover the same parameters as well.
Eigen on cor matrix
Eigenvalue decomposition on correlation matrix.
ez_t1_pca_3 = principal(res_clean_test_data_ez_522_condition, nfactors=3, rotate="oblimin")
Scree plot of first 10 components
data.frame(ez_t1_pca_3$values) %>%
rename(eig = ez_t1_pca_3.values) %>%
arrange(-eig) %>%
mutate(var_pct = eig/sum(eig)*100,
pc = 1:n()) %>%
filter(pc<11)%>%
ggplot(aes(factor(pc), var_pct))+
geom_bar(stat="identity")+
ylab("Percentage of variance explained")+
xlab("Principal component")

Difference in percentage of variance explained is below 1% after the 3 component.
data.frame(ez_t1_pca_3$values) %>%
rename(eig = ez_t1_pca_3.values) %>%
arrange(-eig) %>%
mutate(var_pct = eig/sum(eig)*100,
pc = 1:n(),
var_pct_shift = lead(var_pct),
var_pct_diff = var_pct - var_pct_shift)
Visualizing the first three coponents and coloring them by the parameter type.
ez_t1_pca_3_loadings = as.data.frame(ez_t1_pca_3$loadings[])
ez_t1_pca_3_loadings[abs(ez_t1_pca_3_loadings)<0.3]=NA
tmp = ez_t1_pca_3_loadings %>%
mutate(dv = row.names(.)) %>%
select(dv, TC1, TC2, TC3) %>%
mutate(num_loading = 3-(is.na(TC1)+is.na(TC2)+is.na(TC3) ) ) %>%
filter(num_loading!=0) %>%
select(-num_loading) %>%
arrange(-TC1, -TC2, -TC3) %>%
mutate(order_num = 1:n(),
dv = reorder(dv, -order_num)) %>%
select(-order_num) %>%
gather(Factor, Loading, -dv) %>%
na.exclude() %>%
mutate(neg_load = factor(ifelse(Loading>0,"NA","#000000")),
var_type = ifelse(grepl("drift", dv), "Drift rate",
ifelse(grepl("thresh", dv), "Threshold",
ifelse(grepl("non_dec", dv), "Non-decision", NA))),
var_type = factor(var_type, levels=c("Drift rate", "Threshold", "Non-decision")))
p = tmp%>%
ggplot(aes(dv, abs(Loading), fill=var_type, col = neg_load))+
geom_bar(stat = "identity", alpha=0.5)+
facet_wrap(~Factor, nrow=1)+
coord_flip()+
xlab("")+
ylab("Absolute Loading")+
scale_color_identity()+
theme(legend.position = "bottom",
legend.title=element_blank(),
axis.text.y = element_blank())
ggsave('EZ_PCA_T1_3.jpeg',plot=p, device = 'jpeg', path = fig_path, width = 10, height = 12, units = "in")
p

SVD
Double checking the above factor solution using a singular value decomposition and oblimin rotation.
ez_svd <- svd(res_clean_test_data_ez_522_condition)
ncomp = 3
df <- nrow(res_clean_test_data_ez_522_condition) - 1
ez_t1_svd_rawLoadings = ez_svd$v[,1:ncomp] %*% diag(ez_svd$d/sqrt(df), ncomp, ncomp)
ez_t1_svd_rotatedLoadings <- GPArotation::oblimin(ez_t1_svd_rawLoadings)$loadings
ez_t1_svd_rotatedLoadings[abs(ez_t1_svd_rotatedLoadings)<0.3]=NA
tmp = data.frame(ez_t1_svd_rotatedLoadings) %>%
mutate(dv = names(res_clean_test_data_ez_522_condition),
X1 = -1*X1,
X3= -1*X3) %>%
select(dv, X1, X2, X3)%>%
mutate(num_loading = 3-(is.na(X1)+is.na(X2)+is.na(X3) ) ) %>%
filter(num_loading!=0) %>%
select(-num_loading) %>%
arrange(-X1, -X2, -X3) %>%
mutate(order_num = 1:n(),
dv = reorder(dv, -order_num)) %>%
select(-order_num) %>%
gather(Factor, Loading, -dv) %>%
na.exclude() %>%
mutate(neg_load = factor(ifelse(Loading>0,"NA","#000000")),
var_type = ifelse(grepl("drift", dv), "Drift rate",
ifelse(grepl("thresh", dv), "Threshold",
ifelse(grepl("non_dec", dv), "Non-decision", NA))),
var_type = factor(var_type, levels=c("Drift rate", "Threshold", "Non-decision")))
p = tmp%>%
ggplot(aes(dv, abs(Loading), fill=var_type, col = neg_load))+
geom_bar(stat = "identity", alpha=0.5)+
facet_wrap(~Factor, nrow=1)+
coord_flip()+
xlab("")+
ylab("Absolute Loading")+
scale_color_identity()+
theme(legend.position = "bottom",
legend.title=element_blank(),
axis.text.y = element_blank())
ggsave('EZ_SVD_T1_3.jpeg',plot=p, device = 'jpeg', path = fig_path, width = 10, height = 12, units = "in")
p

EFA on EZ variables of test data
How many factors should be extracted from the EFA? To answer this we run models extracting 2 to 50 components and rank them by BIC.
efa_ez_t1_comp_metrics = find_optimal_components(res_clean_test_data_ez_522_condition, fm = "minres", minc=2)
This suggests that a 13 component solution would be the best fitting model.
efa_ez_t1_comp_metrics
Fit the model suggested by the BIC comparison.
ez_t1_fa_13 = fa(res_clean_test_data_ez_522_condition, efa_ez_t1_comp_metrics$comp[1], rotate='oblimin', fm='minres', scores='tenBerge')
Fit the 3 factor model that is of theoretical interest.
ez_t1_fa_3 = fa(res_clean_test_data_ez_522_condition, 3, rotate='oblimin', fm='minres', scores='tenBerge')
Differences in BIC’s are difficult to interpret.
I tried to run a more formal model comparison between the 3 factor solution that we have theoretical reasons for and the 16 factor model that is selected based on BIC. Below is the result. I’m not quiet sure how to interpreted but it seems to suggest that the more complicated model is not significantly better.
anova(ez_t1_fa_3, ez_t1_fa_13)
So what would the 3 factor solution look like? Not surprisingly, very similar to the PC’s.
ez_t1_fa_3_loadings = as.data.frame(ez_t1_fa_3$loadings[])
ez_t1_fa_3_loadings[abs(ez_t1_fa_3_loadings)<0.3]=NA
tmp = ez_t1_fa_3_loadings %>%
mutate(dv = row.names(.)) %>%
select(dv, MR1, MR2, MR3) %>%
mutate(num_loading = 3-(is.na(MR1)+is.na(MR2)+is.na(MR3) ) ) %>%
filter(num_loading!=0) %>%
select(-num_loading) %>%
arrange(-MR1, -MR2, -MR3) %>%
mutate(order_num = 1:n(),
dv = reorder(dv, -order_num)) %>%
select(-order_num) %>%
gather(Factor, Loading, -dv) %>%
na.exclude() %>%
mutate(neg_load = factor(ifelse(Loading>0,"NA","#000000")),
var_type = ifelse(grepl("drift", dv), "Drift rate",
ifelse(grepl("thresh", dv), "Threshold",
ifelse(grepl("non_dec", dv), "Non-decision", NA))),
var_type = factor(var_type, levels=c("Drift rate", "Threshold", "Non-decision")))
p = tmp%>%
ggplot(aes(dv, abs(Loading), fill=var_type, col = neg_load))+
geom_bar(stat = "identity", alpha=0.5)+
facet_wrap(~Factor, nrow=1)+
coord_flip()+
xlab("")+
ylab("Absolute Loading")+
scale_color_identity()+
theme(legend.position = "bottom",
legend.title=element_blank())
# axis.text.y = element_blank())
ggsave('EZ_FA_T1_3_w_labels.jpeg',plot=p, device = 'jpeg', path = fig_path, width = 15, height = 15, units = "in")
p = tmp%>%
ggplot(aes(dv, abs(Loading), fill=var_type, col = neg_load))+
geom_bar(stat = "identity", alpha=0.5)+
facet_wrap(~Factor, nrow=1)+
coord_flip()+
xlab("")+
ylab("Absolute Loading")+
scale_color_identity()+
theme(legend.position = "bottom",
legend.title=element_blank(),
axis.text.y = element_blank())
ggsave('EZ_FA_T1_3.jpeg',plot=p, device = 'jpeg', path = fig_path, width = 10, height = 12, units = "in")
p

PCs vs Factors
Variance acconted for by PCA and EFA
ez_t1_pca_3$Vaccounted
TC1 TC2 TC3
SS loadings 15.9033 11.9575 9.58886
Proportion Var 0.1359 0.1022 0.08196
Cumulative Var 0.1359 0.2381 0.32008
Proportion Explained 0.4247 0.3193 0.25605
Cumulative Proportion 0.4247 0.7440 1.00000
ez_t1_fa_3$Vaccounted
MR1 MR2 MR3
SS loadings 15.3460 11.29760 8.86720
Proportion Var 0.1312 0.09656 0.07579
Cumulative Var 0.1312 0.22772 0.30351
Proportion Explained 0.4322 0.31815 0.24970
Cumulative Proportion 0.4322 0.75030 1.00000
Comparison of loadings
ez_t1_pca_3_loadings %>%
mutate(dv = row.names(.)) %>%
left_join(ez_t1_fa_3_loadings %>%
mutate(dv = row.names(.)), by="dv") %>%
gather(key, value, -dv) %>%
mutate(factor_num = ifelse(grepl("1", key), 1, ifelse(grepl("2", key), 2, ifelse(grepl("3", key), 3, NA))),
key = ifelse(grepl("TC", key), "PCA", ifelse(grepl("MR", key), "EFA", NA))) %>% spread(key, value) %>%
ggplot(aes(PCA, EFA, col=factor(factor_num)))+
geom_point()+
geom_abline(aes(intercept=0, slope = 1), linetype=2)+
facet_wrap(~factor_num)+
theme(legend.position = "none",
panel.grid = element_blank())

Artifical PCA
art_pca_scores = res_clean_test_data_ez_522_condition %>%
mutate(sub_id = test_data_522$sub_id) %>%
gather(dv, value, -sub_id) %>%
left_join(measure_labels %>% select(dv, rt_acc), by="dv") %>%
group_by(sub_id, rt_acc) %>%
summarise(mean_val = mean(value)) %>%
mutate(rt_acc = ifelse(rt_acc == "drift rate", "MR1", ifelse(rt_acc == "non-decision", "MR2", "MR3")))
Warning: attributes are not identical across measure variables;
they will be dropped
art_pca_scores
data.frame(ez_t1_fa_3$scores) %>%
mutate(sub_id = test_data_522$sub_id) %>%
gather(rt_acc, factor_score, -sub_id)%>%
left_join(art_pca_scores, by=c("sub_id", "rt_acc")) %>%
ggplot(aes(mean_val,factor_score, col=rt_acc))+
geom_point()+
geom_abline(aes(intercept=0, slope=1), linetype=2)+
facet_wrap(~rt_acc, scales="free")+
theme(legend.position = "none",
panel.grid = element_blank())+
xlab("Mean value of individual measures")+
ylab("EFA")

Based on these results I ran 3 factor EFA’s on the remaining data.
EFA on HDDM variables of test data
Extract EZ variables
test_data_hddm = test_data %>%
select(grep('hddm', names(test_data), value=T))
Remove variables that are correlated >0.85
clean_test_data_hddm = remove_correlated_task_variables(test_data_hddm)
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Dropping 0 variables with correlations above 0.85
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Remove outliers (>2.5 SD away)
clean_test_data_hddm = as.data.frame(apply(clean_test_data_hddm, 2, remove_outliers))
Transform skewed variables
#Nothing to transform
numeric_cols = get_numeric_cols()
numeric_cols = numeric_cols[numeric_cols %in% names(clean_test_data_hddm) == T]
clean_test_data_hddm = transform_remove_skew(clean_test_data_hddm, numeric_cols)
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Using transformation:log
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
2 data positively skewed data were transformed:
dot_pattern_expectancy.hddm_thresh
stim_selective_stop_signal.hddm_thresh
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
0 positively skewed data could not be transformed successfully:
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
No negatively skewed variables found.* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Drop subject identifier column, mean impute and drop cols with no variance
clean_test_data_hddm_std = clean_test_data_hddm %>% mutate_if(is.numeric, scale)
#mean imputation
clean_test_data_hddm_std[is.na(clean_test_data_hddm_std)]=0
#drop cols with no variance
clean_test_data_hddm_std = clean_test_data_hddm_std %>%
select_if(function(col) sd(col) != 0)
Residualize Age and Sex effects
clean_test_data_hddm_std = cbind(clean_test_data_hddm_std, demographics[,c("Age", "Sex")])
res_clean_test_data_hddm = residualize_baseline(clean_test_data_hddm_std)
Fit the 3 factor model that is of theoretical interest.
hddm_t1_fa_3 = fa(res_clean_test_data_hddm, 3, rotate='oblimin', fm='minres', scores='tenBerge')
Warning in cor.smooth(R): Matrix was not positive definite, smoothing was
done
Warning in cor.smooth(R): Matrix was not positive definite, smoothing was
done
Warning in cor.smooth(R): Matrix was not positive definite, smoothing was
done
Warning in cor.smooth(r): Matrix was not positive definite, smoothing was
done
Warning in fa.stats(r = r, f = f, phi = phi, n.obs = n.obs, np.obs
= np.obs, : The estimated weights for the factor scores are probably
incorrect. Try a different factor extraction method.
Warning in cor.smooth(r): Matrix was not positive definite, smoothing was
done
The three factor model does not work as well for HDDM’s because of the 106 variables we have 75 are drift rates, 17 are threshold and 14 are non-decision times. This is because while all the parameters are fit separately for EZ only the drift rate parameter is allowed to vary by condition for the HDDM. So the model is mostly separating out drift rates from each other.
hddm_t1_fa_3_loadings = as.data.frame(hddm_t1_fa_3$loadings[])
hddm_t1_fa_3_loadings[abs(hddm_t1_fa_3_loadings)<0.3]=NA
tmp = hddm_t1_fa_3_loadings %>%
mutate(dv = row.names(.)) %>%
select(dv, MR1, MR2, MR3) %>%
mutate(num_loading = 3-(is.na(MR1)+is.na(MR2)+is.na(MR3) ) ) %>%
filter(num_loading!=0) %>%
select(-num_loading) %>%
arrange(-MR1, -MR2, -MR3) %>%
mutate(order_num = 1:n(),
dv = reorder(dv, -order_num)) %>%
select(-order_num) %>%
gather(Factor, Loading, -dv) %>%
na.exclude() %>%
mutate(neg_load = factor(ifelse(Loading>0,"NA","#000000")),
var_type = ifelse(grepl("drift", dv), "drif rate",
ifelse(grepl("thresh", dv), "threshold",
ifelse(grepl("non_dec", dv), "non-decision", NA))))
p = tmp%>%
ggplot(aes(dv, abs(Loading), fill=var_type, col = neg_load))+
geom_bar(stat = "identity", alpha=0.5)+
facet_wrap(~Factor, nrow=1)+
coord_flip()+
xlab("")+
ylab("Absolute Loading")+
scale_color_identity()+
theme(legend.position = "bottom",
legend.title=element_blank(),
axis.text.y = element_blank())
ggsave('HDDM_T1_FA_3.jpeg',plot=p, device = 'jpeg', path = fig_path, width = 10, height = 12, units = "in")
p

Does it fit better if I use just the main drift rates and not the condition ones?
hddm_var_subset = c("adaptive_n_back.hddm_drift" , "attention_network_task.hddm_drift", "choice_reaction_time.hddm_drift", "directed_forgetting.hddm_drift", "dot_pattern_expectancy.hddm_drift" , "local_global_letter.hddm_drift", "motor_selective_stop_signal.hddm_drift", "recent_probes.hddm_drift", "shape_matching.hddm_drift" , "simon.hddm_drift", "stim_selective_stop_signal.hddm_drift", "stop_signal.hddm_drift", "stroop.hddm_drift" , "threebytwo.hddm_drift", "adaptive_n_back.hddm_thresh", "attention_network_task.hddm_thresh", "choice_reaction_time.hddm_thresh", "directed_forgetting.hddm_thresh", "dot_pattern_expectancy.hddm_thresh.logTr", "local_global_letter.hddm_thresh", "motor_selective_stop_signal.hddm_thresh", "recent_probes.hddm_thresh", "shape_matching.hddm_thresh", "simon.hddm_thresh", "stim_selective_stop_signal.hddm_thresh.logTr", "stop_signal.hddm_thresh", "stop_signal.hddm_thresh_high", "stop_signal.hddm_thresh_low", "stop_signal.proactive_slowing_hddm_thresh", "stroop.hddm_thresh", "threebytwo.hddm_thresh", grep("non_dec", names(test_data_hddm), value = T))
res_clean_test_data_hddm_subset = res_clean_test_data_hddm %>% select(hddm_var_subset)
hddm_t1_fa_3_subset = fa(res_clean_test_data_hddm_subset, 3, rotate='oblimin', fm='minres', scores='tenBerge')
Warning in cor.smooth(R): Matrix was not positive definite, smoothing was
done
Warning in cor.smooth(R): Matrix was not positive definite, smoothing was
done
Warning in cor.smooth(R): Matrix was not positive definite, smoothing was
done
Warning in cor.smooth(r): Matrix was not positive definite, smoothing was
done
Warning in cor.smooth(r): Matrix was not positive definite, smoothing was
done
Then it works better but still not doing a great job separating out thresholds from non-decision times.
hddm_t1_fa_3_subset_loadings = as.data.frame(hddm_t1_fa_3_subset$loadings[])
hddm_t1_fa_3_subset_loadings[abs(hddm_t1_fa_3_subset_loadings)<0.3]=NA
tmp = hddm_t1_fa_3_subset_loadings %>%
mutate(dv = row.names(.)) %>%
select(dv, MR1, MR2, MR3) %>%
mutate(num_loading = 3-(is.na(MR1)+is.na(MR2)+is.na(MR3) ) ) %>%
filter(num_loading!=0) %>%
select(-num_loading) %>%
arrange(-MR1, -MR2, -MR3) %>%
mutate(order_num = 1:n(),
dv = reorder(dv, -order_num)) %>%
select(-order_num) %>%
gather(Factor, Loading, -dv) %>%
na.exclude() %>%
mutate(neg_load = factor(ifelse(Loading>0,"NA","#000000")),
var_type = ifelse(grepl("drift", dv), "drif rate",
ifelse(grepl("thresh", dv), "threshold",
ifelse(grepl("non_dec", dv), "non-decision", NA))))
p = tmp%>%
ggplot(aes(dv, abs(Loading), fill=var_type, col = neg_load))+
geom_bar(stat = "identity", alpha=0.5)+
facet_wrap(~Factor, nrow=1)+
coord_flip()+
xlab("")+
ylab("Absolute Loading")+
scale_color_identity()+
theme(legend.position = "bottom",
legend.title=element_blank())
#axis.text.y = element_blank())
ggsave('HDDM_T1_FA_3_subset.jpeg',plot=p, device = 'jpeg', path = fig_path, width = 10, height = 12, units = "in")
knitr::include_graphics(paste0(fig_path, 'HDDM_T1_FA_3_subset.jpeg'))

EFA on EZ variables of retest data
Preparing the data
retest_data_ez = retest_data %>%
select(grep('EZ', names(retest_data), value=T))
Remove variables that are correlated >0.85
clean_retest_data_ez = remove_correlated_task_variables(retest_data_ez)
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Dropping 0 variables with correlations above 0.85
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Remove outliers (>2.5 SD away)
clean_retest_data_ez = as.data.frame(apply(clean_retest_data_ez, 2, remove_outliers))
Transform skewed variables
#Nothing to transform
numeric_cols = get_numeric_cols()
numeric_cols = numeric_cols[numeric_cols %in% names(clean_retest_data_ez) == T]
clean_retest_data_ez = transform_remove_skew(clean_retest_data_ez, numeric_cols)
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Using transformation:log
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
No positively skewed variables found.* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
No negatively skewed variables found.* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Drop subject identifier column, mean impute and drop cols with no variance
clean_retest_data_ez_std = clean_retest_data_ez %>% mutate_if(is.numeric, scale)
#mean imputation
clean_retest_data_ez_std[is.na(clean_retest_data_ez_std)]=0
#drop cols with no variance
clean_retest_data_ez_std = clean_retest_data_ez_std %>%
select_if(function(col) sd(col) != 0)
Residualize Age and Sex effects
clean_retest_data_ez_std = cbind(clean_retest_data_ez_std, demographics[,c("Age", "Sex")])
res_clean_retest_data_ez = residualize_baseline(clean_retest_data_ez_std)
There are two ways we can go about this:
- Predicting factor scores for T2 data using the T1 factor solution
- Fitting a new 3-factor model to the T2 data
Predict retest data using the 3 factor EFA from test data
Define helper functions to predict t2 factor scores using different scoring methods and calculating their ICCs with t1 factor scores.
predict_t2_fa_scores = function(t1_df = res_clean_test_data_ez, t2_df = res_clean_retest_data_ez, scores="tenBerge", nfactors = 3,rotate='oblimin',fm='minres', sub_ids = retest_data$sub_id){
require(psych)
require(tidyverse)
t1_fa = fa(t1_df, nfactors, rotate=rotate, fm=fm, scores=scores)
t1_fa_scores = data.frame(t1_fa$scores) %>%
mutate(sub_id = sub_ids)
t2_pred = predict(t1_fa, t2_df)
t2_pred_scores = as.data.frame(t2_pred) %>%
mutate(sub_id = sub_ids)
return(list(t1_fa_scores=t1_fa_scores, t2_pred_scores=t2_pred_scores))
}
get_icc_for_score_type = function(scores){
p = predict_t2_fa_scores(scores=scores)
r_df = make_rel_df(p$t1_fa_scores, p$t2_pred_scores, metrics = c("icc2.1"))
r_df = r_df %>%
spread(dv,icc2.1) %>%
mutate(scores = scores)
return(r_df)
}
Using tenBerge our default option to calculate factor scores
ez_t2_fa_3_pred = predict_t2_fa_scores()
Warning in cor.smooth(R): Matrix was not positive definite, smoothing was
done
Warning in cor.smooth(R): Matrix was not positive definite, smoothing was
done
Warning in cor.smooth(R): Matrix was not positive definite, smoothing was
done
Warning in cor.smooth(r): Matrix was not positive definite, smoothing was
done
The determinant of the smoothed correlation was zero.
This means the objective function is not defined.
Chi square is based upon observed residuals.
The determinant of the smoothed correlation was zero.
This means the objective function is not defined for the null model either.
The Chi square is thus based upon observed correlations.
Warning in fa.stats(r = r, f = f, phi = phi, n.obs = n.obs, np.obs
= np.obs, : The estimated weights for the factor scores are probably
incorrect. Try a different factor extraction method.
Warning in fa.stats(r = r, f = f, phi = phi, n.obs = n.obs, np.obs =
np.obs, : Matrix was not positive definite, smoothing was done
Plotting predicted tenBerge scores for T2 data using T1 FA against T1 FA scores.
ez_t2_fa_3_pred$t1_fa_scores %>%
mutate(time = "t1_fa_scores") %>%
rbind(ez_t2_fa_3_pred$t2_pred_scores %>% mutate(time="t2_pred_scores")) %>%
gather(factor, score, -sub_id, -time) %>%
spread(time, score) %>%
ggplot(aes(t1_fa_scores, t2_pred_scores, col=factor))+
geom_point()+
geom_abline(aes(slope=1, intercept=0))+
facet_wrap(~factor, scales='free')+
theme(legend.position = "none")

No relationship! As a result ICC’s are 0 too.
BUT tenBerge is not the only way of calculating factor scores. psych offers different methods for calculating factor scores based on Grice (2001). Here I calculate the ICC’s of the three factors using other methods of calculating the scores.
Regardless of which method I use I get the following warning
The estimated weights for the factor scores are probably incorrect. Try a different factor extraction method.
I tried all five and only three gave me factor scores despite this warning. But they imply different reliabilities. Anderson and Harman yield high ICC’s for all three factors where tenBerge says it should be 0.
score_type_iccs = rbind(get_icc_for_score_type("tenBerge"),
get_icc_for_score_type("Anderson"),
get_icc_for_score_type("Harman"))
score_type_iccs
score_type_iccs %>%
gather(factor, icc2.1, -scores) %>%
ggplot(aes(scores, icc2.1, col=factor))+
geom_point(size=2.5)+
xlab('')+
theme(legend.title=element_blank())

Plotting the scores using Anderson as an alternative example. Reflecting the high ICC’s now all factor scores for both time points are highly correlated.
ez_t2_fa_3_pred_bartlett = predict_t2_fa_scores(scores="Anderson")
ez_t2_fa_3_pred_bartlett$t1_fa_scores %>%
mutate(time = "t1_fa_scores") %>%
rbind(ez_t2_fa_3_pred_bartlett$t2_pred_scores %>% mutate(time="t2_pred_scores")) %>%
gather(factor, score, -sub_id, -time) %>%
spread(time, score) %>%
ggplot(aes(t1_fa_scores, t2_pred_scores, col=factor))+
geom_point()+
geom_abline(aes(slope=1, intercept=0))+
facet_wrap(~factor, scales='free')+
theme(legend.position = "none")

Based on the warning in fitting the models I dug into the psych package to find the different weight matrix calculations depending on the score methods:
where x is data and r <- cor(x, use = “pairwise”) f <- loadings(factor_model) Phi <- factor_model$Phi
Thurston
S <- f %*% Phi
w <- try(solve(r, S), silent = TRUE)
tenBerge
L <- f %*% matSqrt(Phi)
r.5 <- invMatSqrt(r)
r <- cor.smooth(r)
inv.r <- try(solve(r), silent = TRUE)
C <- r.5 %*% L %*% invMatSqrt(t(L) %*% inv.r %*% L)
w <- r.5 %*% C %*% matSqrt(Phi)
Anderson
I <- diag(1, nf, nf)
h2 <- diag(f %*% Phi %*% t(f))
U2 <- 1 - h2
inv.U2 <- diag(1/U2)
w <- inv.U2 %*% f %*% invMatSqrt(t(f) %*% inv.U2 %*% r %*% inv.U2 %*% f)
Bartlett
I <- diag(1, nf, nf)
h2 <- diag(f %*% Phi %*% t(f))
U2 <- 1 - h2
inv.U2 <- diag(1/U2)
w <- inv.U2 %*% f %*% (solve(t(f) %*% inv.U2 %*% f))
Harman
m <- f %*% t(S)
diag(m) <- 1
inv.m <- solve(m)
w <- inv.m %*% f
I’m stuck here though and can’t figure out what the problem with the tenBerge weight matrix is or what to make of different reliability results depending on the scoring method.
Fit a 3 factor model separately for T2 data.
Instead of predicting T2 data from the T1 factor solution we can also fit a 3 factor model to the T2 data independently.
Conceptually this might be similar to fitting the DDM’s separately for two time points when generating parameter estimates instead of using the estimates from the first time point for each subject.
ez_t2_fa_3 = fa(res_clean_retest_data_ez, 3, rotate='oblimin', fm='minres', scores='tenBerge')
Warning in cor.smooth(R): Matrix was not positive definite, smoothing was
done
Warning in cor.smooth(R): Matrix was not positive definite, smoothing was
done
Warning in cor.smooth(R): Matrix was not positive definite, smoothing was
done
Warning in cor.smooth(r): Matrix was not positive definite, smoothing was
done
The determinant of the smoothed correlation was zero.
This means the objective function is not defined.
Chi square is based upon observed residuals.
The determinant of the smoothed correlation was zero.
This means the objective function is not defined for the null model either.
The Chi square is thus based upon observed correlations.
Warning in fa.stats(r = r, f = f, phi = phi, n.obs = n.obs, np.obs
= np.obs, : The estimated weights for the factor scores are probably
incorrect. Try a different factor extraction method.
Warning in fa.stats(r = r, f = f, phi = phi, n.obs = n.obs, np.obs =
np.obs, : Matrix was not positive definite, smoothing was done
Plot of independent 3 factor solution for T2 data looks very similar to that of T1 solution (with two factors switched names)
ez_t2_fa_3_loadings = as.data.frame(ez_t2_fa_3$loadings[])
ez_t2_fa_3_loadings[abs(ez_t2_fa_3_loadings)<0.3]=NA
tmp = ez_t2_fa_3_loadings %>%
mutate(dv = row.names(.)) %>%
select(dv, MR1, MR2, MR3) %>%
mutate(num_loading = 3-(is.na(MR1)+is.na(MR2)+is.na(MR3) ) ) %>%
filter(num_loading!=0) %>%
select(-num_loading) %>%
arrange(-MR1, -MR2, -MR3) %>%
mutate(order_num = 1:n(),
dv = reorder(dv, -order_num)) %>%
select(-order_num) %>%
gather(Factor, Loading, -dv) %>%
na.exclude() %>%
mutate(neg_load = factor(ifelse(Loading>0,"NA","#000000")),
var_type = ifelse(grepl("drift", dv), "drif rate",
ifelse(grepl("thresh", dv), "threshold",
ifelse(grepl("non_dec", dv), "non-decision", NA))))
p = tmp%>%
ggplot(aes(dv, abs(Loading), fill=var_type, col = neg_load))+
geom_bar(stat = "identity", alpha=0.5)+
facet_wrap(~Factor, nrow=1)+
coord_flip()+
xlab("")+
scale_color_identity()+
ylab("Absolute Loading")+
theme(legend.position = "bottom",
legend.title=element_blank())
ggsave('EZ_FA_T2_3_w_labels.jpeg',plot=p, device = 'jpeg', path = fig_path, width = 15, height = 15, units = "in")
p = tmp%>%
ggplot(aes(dv, abs(Loading), fill=var_type, col = neg_load))+
geom_bar(stat = "identity", alpha=0.5)+
facet_wrap(~Factor, nrow=1)+
coord_flip()+
xlab("")+
scale_color_identity()+
ylab("Absolute Loading")+
theme(legend.position = "bottom",
legend.title=element_blank(),
axis.text.y = element_blank())
ggsave('EZ_FA_T2_3.jpeg',plot=p, device = 'jpeg', path = fig_path, width = 10, height = 12, units = "in")
p

T1_fit scores vs T2_fit scores
ez_t2_fa_3_scores = data.frame(ez_t2_fa_3$scores)
ez_t2_fa_3_scores$sub_id = retest_data$sub_id
data.frame(ez_t1_fa_3$scores) %>%
mutate(sub_id = test_data_522$sub_id) %>%
left_join(ez_t2_fa_3_scores, by = "sub_id") %>%
gather(key, value, -sub_id) %>%
separate(key, c("par", "model"), sep = "\\.") %>%
mutate(model = ifelse(model=="y", "T2_fit","T1_fit")) %>%
spread(model, value)%>%
ggplot(aes(T1_fit, T2_fit))+
geom_point()+
facet_wrap(~par, scales= "free")+
geom_abline(aes(slope=1, intercept=0))
Warning: Removed 1116 rows containing missing values (geom_point).

T1_fit vs T2_fit loadings
ez_t1_fa_3_loadings = data.frame(ez_t1_fa_3$loadings[]) %>%
rename(drift_rates = MR1, thresholds = MR2, non_decs = MR3) %>%
mutate(dv = row.names(.))
ez_t2_fa_3_loadings = data.frame(ez_t2_fa_3$loadings[]) %>%
rename(drift_rates = MR1, thresholds = MR3, non_decs = MR2)%>%
mutate(dv = row.names(.))
ez_t1_fa_3_loadings %>%
left_join(ez_t2_fa_3_loadings, by = "dv") %>%
gather(key, value, -dv) %>%
separate(key, c("par", "model"), sep = "\\.") %>%
mutate(model = ifelse(model == "x", "T1_fit", "T2_fit"))%>%
spread(model, value) %>%
ggplot(aes(T1_fit, T2_fit))+
geom_point()+
geom_abline(aes(slope=1, intercept=0))+
facet_wrap(~par)

Model time as a latent variable
Dave’s response to my confusions on this:
The standard way to evaluate test-retest correlations with latent variables is to fit a latent variable for each time and then allow the latent variables to correlate. That is the test-retest correlation for the best, “true” score measure at each time point.
Based on this I tried modeling all the data together using timepoint it was collected in as a latent variable. The details of those analyses are here
EFA on HDDM variables of retest data
retest_data_hddm = retest_data %>%
select(grep('hddm', names(retest_data), value=T))
Remove variables that are correlated >0.85
clean_retest_data_hddm = remove_correlated_task_variables(retest_data_hddm)
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Dropping 0 variables with correlations above 0.85
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Remove outliers (>2.5 SD away)
clean_retest_data_hddm = as.data.frame(apply(clean_retest_data_hddm, 2, remove_outliers))
Transform skewed variables as in T1
clean_retest_data_hddm = clean_retest_data_hddm %>%
mutate(dot_pattern_expectancy.hddm_thresh.logTr = log(dot_pattern_expectancy.hddm_thresh),
stim_selective_stop_signal.hddm_thresh.logTr = log(stim_selective_stop_signal.hddm_thresh)) %>%
select(-dot_pattern_expectancy.hddm_thresh,-stim_selective_stop_signal.hddm_thresh)
Drop subject identifier column, mean impute and drop cols with no variance
clean_retest_data_hddm_std = clean_retest_data_hddm %>% mutate_if(is.numeric, scale)
#mean imputation
clean_retest_data_hddm_std[is.na(clean_retest_data_hddm_std)]=0
#drop cols with no variance
clean_retest_data_hddm_std = clean_retest_data_hddm_std %>%
select_if(function(col) sd(col) != 0)
Residualize Age and Sex effects
clean_retest_data_hddm_std = cbind(clean_retest_data_hddm_std, demographics[,c("Age", "Sex")])
res_clean_retest_data_hddm = residualize_baseline(clean_retest_data_hddm_std)
Select only the vars that went in to the limited set of hddm variables
res_clean_retest_data_hddm_subset = res_clean_retest_data_hddm %>%
select(hddm_var_subset)
Predict using the 3 factor EFA from T1
hddm_t2_fa_3_subset = predict(hddm_t1_fa_3_subset, res_clean_retest_data_hddm_subset)
LS0tCnRpdGxlOiAnRGlmZmVyZW50IGFwcHJvYWNoZXMgdG8gZGltZW5zaW9uYWxpdHkgcmVkdWN0aW9uIG9mIERETSB2YXJpYWJsZXMnCm91dHB1dDoKZ2l0aHViX2RvY3VtZW50Ogp0b2M6IHllcwp0b2NfZmxvYXQ6IHllcwotLS0KCmBgYHtyfQpzb3VyY2UoJ34vRHJvcGJveC9Qb2xkcmFja0xhYi9TUk9fRERNX0FuYWx5c2VzL2NvZGUvd29ya3NwYWNlX3NjcmlwdHMvZXpfZmFfZGF0YS5SJykKZmlnX3BhdGggPSAnL1VzZXJzL3pleW5lcGVua2F2aS9Ecm9wYm94L1BvbGRyYWNrTGFiL1NST19ERE1fQW5hbHlzZXMvb3V0cHV0L2ZpZ3VyZXMvJwpzb3VyY2UoJ34vRHJvcGJveC9Qb2xkcmFja0xhYi9TUk9fUmV0ZXN0X0FuYWx5c2VzL2NvZGUvZmlndXJlX3NjcmlwdHMvZmlndXJlX3Jlc193cmFwcGVyLlInKQpzb3VyY2UoJ34vRHJvcGJveC9Qb2xkcmFja0xhYi9TUk9fUmV0ZXN0X0FuYWx5c2VzL2NvZGUvaGVscGVyX2Z1bmN0aW9ucy9tYWtlX3JlbF9kZi5SJykKYGBgCgpUbyByZWR1Y2UgdGhlIG51bWJlciBvZiB2YXJpYWJsZXMgaW4gYSBkYXRhLWRyaXZlbiB3YXkgKGluc3RlYWQgb2YganVzdCBzZWxlY3RpbmcgdGhlIHZhcmlhYmxlcyB0aGF0IHdlbnQgaW4gdG8gdGhlIG9udG9sb2d5IHBhcGVyKSAgSSdsbCBhcHBseSB0aGUgY2xlYW5pbmcgbWV0aG9kcyBmcm9tIHRoZSBvbnRvbG9neSBwaXBlbGluZToKLSBkcm9wcGluZyB2YXJpYWJsZXMgd2l0aCByPjAuODUuCi0gUmVtb3ZlIG91dGxpZXJzICg+Mi41IFNEIGF3YXkpCi0gdHJhbnNmb3JtYXRpb24gb2Ygbm9uLW5vcm1hbCB2YXJpYWJsZXMgKHNob3VsZCBiZSBwYXJ0aWN1bGFybHkgdXNlZnVsIGZvciBhIHNldCBvZiB2YXJpYWJsZXMgd2l0aCBtYW55IHJlc3BvbnNlIHRpbWVzKSAKLSBSZXNpZHVhbGl6ZSBBZ2UgYW5kIFNleCBlZmZlY3RzCgpgYGB7cn0KcmVzX2NsZWFuX3Rlc3RfZGF0YV9lel81MjJfY29uZGl0aW9uID0gcmVzX2NsZWFuX3Rlc3RfZGF0YV9lel81MjIgJT4lCiAgc2VsZWN0KChtZWFzdXJlX2xhYmVscyAlPiUgCiAgICAgICAgICAgIGZpbHRlcihyYXdfZml0ID09ICJFWiIgJiBvdmVyYWxsX2RpZmZlcmVuY2UgPT0gImNvbmRpdGlvbiIpKSRkdikKYGBgCgojIyBQQ0Egb24gRVogdmFyaWFibGVzIG9mIHRlc3QgZGF0YQoKIyMjIE51bWJlciBvZiB2YXJpYWJsZXMgYW5hbHlzaXMKCkluIHRoaXMgZGF0YXNldCB0aGVyZSBhcmUgbW9yZSB2YXJpYWJsZXMgdGhhbiBvYnNlcnZhdGlvbnMuCgpHaXZlbiB0aGUgbGFyZ2UgbnVtYmVyIG9mIG1lYXN1cmVzIHNvbWUgY2FuIHBvc3NpYmxlIGJlIHJlcHJlc2VudGVkIGFzIGxpbmVhciBjb21iaW5hdGlvbnMgYXMgb3RoZXJzLgoKVGhpcyB3b3VsZCByZXN1bHQgaW4gMCBvciBuZWdhdGl2ZSB2YWx1ZXMgaW4gdGhlIGVpZ2VudmFsdWVzIG9mIHRoZSBjb3JyZWxhdGlvbiBtYXRyaXggYW5kIHdvdWxkIG1lYW4gdGhhdCB0aGUgY29ycmVsYXRpb24gbWF0cml4IGlzIG5vdCBwb3NpdGl2ZSBkZWZpbml0ZS4KCkl0IGlzIGhhcmQgdG8gZGV0ZWN0IHN1Y2ggbXVsdGlwbGUgZGVwZW5kZW5jaWVzLiBHaXZlbiB0aGUgbnVtYmVyIG9mIHBvc3NpYmxlIGNvbWJpbmF0aW9ucyBvZiB2YXJpYWJsZXMgZnJvbSBhbGwgcG9zc2libGUgdmFyaWFibGVzIGl0IGlzIGFsc28gaW1wb3NzaWJsZSB0byBjb21wdXRlIHRoZSBleGFjdCBsYXJnZXN0IGNvbWJpbmF0aW9uIG9mIHZhcmlhYmxlcyB0aGF0IGhhcyBhIHBvc2l0aXZlIGRlZmluaXRlIGNvcnJlbGF0aW9uIG1hdHJpeC4KClNvIGluc3RlYWQgSSB1c2VkIGEgc2FtcGxpbmcgYXBwcm9hY2guIEJlbG93IGlzIGEgcGxvdCBvZiB0aGUgcHJvcG9ydGlvbiBvZiBwb3NpdGl2ZSBkZWZpbml0ZSBtYXRyaWNlcyBvdXQgb2YgMTAwMCBzYW1wbGVzIG9mIHRoZSBnaXZlbiBudW1iZXJzIG9mIHZhcmlhYmxlcyBkcmF3biBmcm9tIGFsbCB2YXJpYWJsZXMuCgpUaGUgcGxvdCBzaG93cyB0aGF0IHRoZXJlIGlzIGEgc3RlYWR5IGRlY3JlYXNlIGluIHRoZSBwcm9wb3J0aW9uIG9mIHBvc2l0aXZlIGRlZmluaXRlIG1hdHJpY2VzIHRoZSBtb3JlIHZhcmlhYmxlcyBhcmUgdXNlZC4gSXQgc2hhcnBseSBkcm9wcyB0byAwIGZyb20gY2hhbmNlIGF0IDE2MCB2YXJpYWJsZXMuCgpgYGB7cn0KbWF4X251bV92YXJzX3N1bW1hcnkgPSByZWFkLmNzdignL1VzZXJzL3pleW5lcGVua2F2aS9Ecm9wYm94L1BvbGRyYWNrTGFiL1NST19ERE1fQW5hbHlzZXMvaW5wdXQvbWF4X251bV92YXJzX3N1bW1hcnkuY3N2JykKCnAgPSBtYXhfbnVtX3ZhcnNfc3VtbWFyeSAlPiUKICBnZ3Bsb3QoYWVzKGFzLmZhY3RvcihjdXJfbnVtX3ZhcnMpLCBwcm9wX2RldF9wb3MpKSsKICBnZW9tX3BvaW50KCkrCiAgZ2VvbV9saW5lKGdyb3VwPTEpKwogIHhsYWIoIk51bWJlciBvZiB2YXJpYWJsZXMgZHJhd24iKSsKICB5bGFiKCJQcm9wb3J0aW9uIG9mIHBvc2l0aXZlIGRlZmluaXRlIGNvcnJlbGF0aW9uIG1hdHJpY2VzIikrCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA5MCkpCgpnZ3NhdmUoJ21heF9udW1fdmFycy5qcGVnJyxwbG90PXAsIGRldmljZSA9ICdqcGVnJywgcGF0aCA9IGZpZ19wYXRoLCB3aWR0aCA9MTIsIGhlaWdodCA9IDQsIHVuaXRzID0gImluIikKCnAKYGBgCgpUaGlzIG1lYW5zIHRoYXQgYW55IGVmZm9ydCB0byByZWR1Y2UgZGltZW5zaW9uYWxpdHkgdXNpbmcgYW4gZWlnZW52YWx1ZSBkZWNvbXBvc2l0aW9uIHdpbGwgZW5jb3VudGVyIHByb2JsZW1zLgoKRm9yIG51bWVyaWNhbCBlZmZpY2llbmN5IGFuZCBzdGFiaWxpdHkgbWFueSBQQ0EgYXBwbGljYXRpb25zIHVzZSBzaW5ndWxhciB2YWx1ZSBkZWNvbXBvc2l0aW9uIGluc3RlYWQgKGUuZy4gc2VlIGRvY3VtZW50YXRpb24gZm9yIHRoZSBgcHJpbmNvbXBgIGFuZCBgcHJjb21wYCBmdW5jdGlvbnMgaW4gdGhlIGBiYXNlYCBwYWNrYWdlKS4KClRvIGJlIGNvbnNpc3RlbnQgd2l0aCB0aGUgZGltZW5zaW9uYWxpdHkgcmVkdWN0aW9uIG1ldGhvZHMgdXNlZCBpbiB0aGUgb250b2xvZ3kgcGFwZXIgSSB3aWxsIHVzZSB0aGUgYHBzeWNoYCBwYWNrYWdlIGZ1bmN0aW9ucyBmb3IgUENBIGFuZCBFRkEuIFRoZXNlIHJlbHkgb24gYW4gZWlnZW52YWx1ZSBkZWNvbXBvc2l0aW9uIGJ1dCBoYXZlIGEgYnVpbHQtaW4gZnVuY3Rpb24gdG8gc21vb3RoIG92ZXIgbmVnYXRpdmUgZWlnZW52YWx1ZXMgdGhhdCBwcmludHMgYSB3YXJuaW5nIGJ1dCBjb250aW51ZXMgdG8gZXhlY3V0ZSBhbmQgeWllbGQgYSBzb2x1dGlvbi4KCkJ1dCB0byBjb25maXJtIHRoYXQgdGhlIHJlc3VsdGluZyBmYWN0b3JzIGFyZSBub3QgcHJvYmxlbWF0aWMgSSB3aWxsIHJ1biBhbiBTVkQgYW5kIHJlY292ZXIgdGhlIHNhbWUgcGFyYW1ldGVycyBhcyB3ZWxsLgoKIyMjIEVpZ2VuIG9uIGNvciBtYXRyaXgKCkVpZ2VudmFsdWUgZGVjb21wb3NpdGlvbiBvbiBjb3JyZWxhdGlvbiBtYXRyaXguCgpgYGB7cn0KZXpfdDFfcGNhXzMgPSBwcmluY2lwYWwocmVzX2NsZWFuX3Rlc3RfZGF0YV9lel81MjJfY29uZGl0aW9uLCBuZmFjdG9ycz0zLCByb3RhdGU9Im9ibGltaW4iKQpgYGAKClNjcmVlIHBsb3Qgb2YgZmlyc3QgMTAgY29tcG9uZW50cwoKYGBge3J9CmRhdGEuZnJhbWUoZXpfdDFfcGNhXzMkdmFsdWVzKSAlPiUKICByZW5hbWUoZWlnID0gZXpfdDFfcGNhXzMudmFsdWVzKSAlPiUKICBhcnJhbmdlKC1laWcpICU+JQogIG11dGF0ZSh2YXJfcGN0ID0gZWlnL3N1bShlaWcpKjEwMCwKICAgICAgICAgcGMgPSAxOm4oKSkgJT4lCiAgZmlsdGVyKHBjPDExKSU+JQogIGdncGxvdChhZXMoZmFjdG9yKHBjKSwgdmFyX3BjdCkpKwogIGdlb21fYmFyKHN0YXQ9ImlkZW50aXR5IikrCiAgeWxhYigiUGVyY2VudGFnZSBvZiB2YXJpYW5jZSBleHBsYWluZWQiKSsKICB4bGFiKCJQcmluY2lwYWwgY29tcG9uZW50IikKYGBgCgpEaWZmZXJlbmNlIGluIHBlcmNlbnRhZ2Ugb2YgdmFyaWFuY2UgZXhwbGFpbmVkIGlzIGJlbG93IDElIGFmdGVyIHRoZSAzIGNvbXBvbmVudC4KCmBgYHtyfQpkYXRhLmZyYW1lKGV6X3QxX3BjYV8zJHZhbHVlcykgJT4lCiAgcmVuYW1lKGVpZyA9IGV6X3QxX3BjYV8zLnZhbHVlcykgJT4lCiAgYXJyYW5nZSgtZWlnKSAlPiUKICBtdXRhdGUodmFyX3BjdCA9IGVpZy9zdW0oZWlnKSoxMDAsCiAgICAgICAgIHBjID0gMTpuKCksCiAgICAgICAgIHZhcl9wY3Rfc2hpZnQgPSBsZWFkKHZhcl9wY3QpLAogICAgICAgICB2YXJfcGN0X2RpZmYgPSB2YXJfcGN0IC0gdmFyX3BjdF9zaGlmdCkKYGBgCgpWaXN1YWxpemluZyB0aGUgZmlyc3QgdGhyZWUgY29wb25lbnRzIGFuZCBjb2xvcmluZyB0aGVtIGJ5IHRoZSBwYXJhbWV0ZXIgdHlwZS4KCmBgYHtyIG1lc3NhZ2U9RkFMU0V9CmV6X3QxX3BjYV8zX2xvYWRpbmdzID0gYXMuZGF0YS5mcmFtZShlel90MV9wY2FfMyRsb2FkaW5nc1tdKQoKZXpfdDFfcGNhXzNfbG9hZGluZ3NbYWJzKGV6X3QxX3BjYV8zX2xvYWRpbmdzKTwwLjNdPU5BCgp0bXAgPSBlel90MV9wY2FfM19sb2FkaW5ncyAlPiUKICBtdXRhdGUoZHYgPSByb3cubmFtZXMoLikpICU+JQogIHNlbGVjdChkdiwgVEMxLCBUQzIsIFRDMykgJT4lCiAgbXV0YXRlKG51bV9sb2FkaW5nID0gMy0oaXMubmEoVEMxKStpcy5uYShUQzIpK2lzLm5hKFRDMykgKSApICU+JQogIGZpbHRlcihudW1fbG9hZGluZyE9MCkgJT4lCiAgc2VsZWN0KC1udW1fbG9hZGluZykgJT4lCiAgYXJyYW5nZSgtVEMxLCAtVEMyLCAtVEMzKSAlPiUKICBtdXRhdGUob3JkZXJfbnVtID0gMTpuKCksCiAgICAgICAgIGR2ID0gcmVvcmRlcihkdiwgLW9yZGVyX251bSkpICU+JQogIHNlbGVjdCgtb3JkZXJfbnVtKSAlPiUKICBnYXRoZXIoRmFjdG9yLCBMb2FkaW5nLCAtZHYpICU+JQogIG5hLmV4Y2x1ZGUoKSAlPiUKICBtdXRhdGUobmVnX2xvYWQgPSBmYWN0b3IoaWZlbHNlKExvYWRpbmc+MCwiTkEiLCIjMDAwMDAwIikpLAogICAgICAgICB2YXJfdHlwZSA9IGlmZWxzZShncmVwbCgiZHJpZnQiLCBkdiksICJEcmlmdCByYXRlIiwKICAgICAgICAgICAgICAgICAgaWZlbHNlKGdyZXBsKCJ0aHJlc2giLCBkdiksICJUaHJlc2hvbGQiLAogICAgICAgICAgICAgICAgICAgICAgICAgaWZlbHNlKGdyZXBsKCJub25fZGVjIiwgZHYpLCAiTm9uLWRlY2lzaW9uIiwgTkEpKSksCiAgICAgICAgIHZhcl90eXBlID0gZmFjdG9yKHZhcl90eXBlLCBsZXZlbHM9YygiRHJpZnQgcmF0ZSIsICJUaHJlc2hvbGQiLCAiTm9uLWRlY2lzaW9uIikpKQoKcCA9IHRtcCU+JQogIGdncGxvdChhZXMoZHYsIGFicyhMb2FkaW5nKSwgZmlsbD12YXJfdHlwZSwgY29sID0gbmVnX2xvYWQpKSsKICBnZW9tX2JhcihzdGF0ID0gImlkZW50aXR5IiwgYWxwaGE9MC41KSsKICBmYWNldF93cmFwKH5GYWN0b3IsIG5yb3c9MSkrCiAgY29vcmRfZmxpcCgpKwogIHhsYWIoIiIpKwogIHlsYWIoIkFic29sdXRlIExvYWRpbmciKSsKICBzY2FsZV9jb2xvcl9pZGVudGl0eSgpKwogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJib3R0b20iLAogICAgICAgIGxlZ2VuZC50aXRsZT1lbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50ZXh0LnkgPSBlbGVtZW50X2JsYW5rKCkpCgpnZ3NhdmUoJ0VaX1BDQV9UMV8zLmpwZWcnLHBsb3Q9cCwgZGV2aWNlID0gJ2pwZWcnLCBwYXRoID0gZmlnX3BhdGgsIHdpZHRoID0gMTAsIGhlaWdodCA9IDEyLCB1bml0cyA9ICJpbiIpCgpwCmBgYAoKIyMjIFNWRAoKRG91YmxlIGNoZWNraW5nIHRoZSBhYm92ZSBmYWN0b3Igc29sdXRpb24gdXNpbmcgYSBzaW5ndWxhciB2YWx1ZSBkZWNvbXBvc2l0aW9uIGFuZCBvYmxpbWluIHJvdGF0aW9uLgoKYGBge3J9CmV6X3N2ZCA8LSBzdmQocmVzX2NsZWFuX3Rlc3RfZGF0YV9lel81MjJfY29uZGl0aW9uKQpuY29tcCA9IDMKZGYgPC0gbnJvdyhyZXNfY2xlYW5fdGVzdF9kYXRhX2V6XzUyMl9jb25kaXRpb24pIC0gMQplel90MV9zdmRfcmF3TG9hZGluZ3MgPSBlel9zdmQkdlssMTpuY29tcF0gJSolIGRpYWcoZXpfc3ZkJGQvc3FydChkZiksIG5jb21wLCBuY29tcCkKZXpfdDFfc3ZkX3JvdGF0ZWRMb2FkaW5ncyA8LSBHUEFyb3RhdGlvbjo6b2JsaW1pbihlel90MV9zdmRfcmF3TG9hZGluZ3MpJGxvYWRpbmdzCmBgYAoKYGBge3IgbWVzc2FnZT1GQUxTRX0KZXpfdDFfc3ZkX3JvdGF0ZWRMb2FkaW5nc1thYnMoZXpfdDFfc3ZkX3JvdGF0ZWRMb2FkaW5ncyk8MC4zXT1OQQoKdG1wID0gZGF0YS5mcmFtZShlel90MV9zdmRfcm90YXRlZExvYWRpbmdzKSAlPiUKICBtdXRhdGUoZHYgPSBuYW1lcyhyZXNfY2xlYW5fdGVzdF9kYXRhX2V6XzUyMl9jb25kaXRpb24pLAogICAgICAgICBYMSA9IC0xKlgxLAogICAgICAgICBYMz0gLTEqWDMpICU+JQogIHNlbGVjdChkdiwgWDEsIFgyLCBYMyklPiUKICBtdXRhdGUobnVtX2xvYWRpbmcgPSAzLShpcy5uYShYMSkraXMubmEoWDIpK2lzLm5hKFgzKSApICkgJT4lCiAgZmlsdGVyKG51bV9sb2FkaW5nIT0wKSAlPiUKICBzZWxlY3QoLW51bV9sb2FkaW5nKSAlPiUKICBhcnJhbmdlKC1YMSwgLVgyLCAtWDMpICU+JQogIG11dGF0ZShvcmRlcl9udW0gPSAxOm4oKSwKICAgICAgICAgZHYgPSByZW9yZGVyKGR2LCAtb3JkZXJfbnVtKSkgJT4lCiAgc2VsZWN0KC1vcmRlcl9udW0pICU+JQogIGdhdGhlcihGYWN0b3IsIExvYWRpbmcsIC1kdikgJT4lCiAgbmEuZXhjbHVkZSgpICU+JQogIG11dGF0ZShuZWdfbG9hZCA9IGZhY3RvcihpZmVsc2UoTG9hZGluZz4wLCJOQSIsIiMwMDAwMDAiKSksCiAgICAgICAgIHZhcl90eXBlID0gaWZlbHNlKGdyZXBsKCJkcmlmdCIsIGR2KSwgIkRyaWZ0IHJhdGUiLAogICAgICAgICAgICAgICAgICAgICAgICAgICBpZmVsc2UoZ3JlcGwoInRocmVzaCIsIGR2KSwgIlRocmVzaG9sZCIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpZmVsc2UoZ3JlcGwoIm5vbl9kZWMiLCBkdiksICJOb24tZGVjaXNpb24iLCBOQSkpKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgdmFyX3R5cGUgPSBmYWN0b3IodmFyX3R5cGUsIGxldmVscz1jKCJEcmlmdCByYXRlIiwgIlRocmVzaG9sZCIsICJOb24tZGVjaXNpb24iKSkpCgpwID0gdG1wJT4lCiAgZ2dwbG90KGFlcyhkdiwgYWJzKExvYWRpbmcpLCBmaWxsPXZhcl90eXBlLCBjb2wgPSBuZWdfbG9hZCkpKwogIGdlb21fYmFyKHN0YXQgPSAiaWRlbnRpdHkiLCBhbHBoYT0wLjUpKwogIGZhY2V0X3dyYXAofkZhY3RvciwgbnJvdz0xKSsKICBjb29yZF9mbGlwKCkrCiAgeGxhYigiIikrCiAgeWxhYigiQWJzb2x1dGUgTG9hZGluZyIpKwogIHNjYWxlX2NvbG9yX2lkZW50aXR5KCkrCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gImJvdHRvbSIsCiAgICAgICAgbGVnZW5kLnRpdGxlPWVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRleHQueSA9IGVsZW1lbnRfYmxhbmsoKSkKCmdnc2F2ZSgnRVpfU1ZEX1QxXzMuanBlZycscGxvdD1wLCBkZXZpY2UgPSAnanBlZycsIHBhdGggPSBmaWdfcGF0aCwgd2lkdGggPSAxMCwgaGVpZ2h0ID0gMTIsIHVuaXRzID0gImluIikKCnAKYGBgCgojIyBFRkEgb24gRVogdmFyaWFibGVzIG9mIHRlc3QgZGF0YQoKSG93IG1hbnkgZmFjdG9ycyBzaG91bGQgYmUgZXh0cmFjdGVkIGZyb20gdGhlIEVGQT8gVG8gYW5zd2VyIHRoaXMgd2UgcnVuIG1vZGVscyBleHRyYWN0aW5nIDIgdG8gNTAgY29tcG9uZW50cyBhbmQgcmFuayB0aGVtIGJ5IEJJQy4KCmBgYHtyIHdhcm5pbmc9RkFMU0UsIG1lc3NhZ2U9RkFMU0V9CmVmYV9lel90MV9jb21wX21ldHJpY3MgPSBmaW5kX29wdGltYWxfY29tcG9uZW50cyhyZXNfY2xlYW5fdGVzdF9kYXRhX2V6XzUyMl9jb25kaXRpb24sIGZtID0gIm1pbnJlcyIsIG1pbmM9MikKYGBgCgpUaGlzIHN1Z2dlc3RzIHRoYXQgYSAxMyBjb21wb25lbnQgc29sdXRpb24gd291bGQgYmUgdGhlIGJlc3QgZml0dGluZyBtb2RlbC4KCmBgYHtyfQplZmFfZXpfdDFfY29tcF9tZXRyaWNzCmBgYAoKRml0IHRoZSBtb2RlbCBzdWdnZXN0ZWQgYnkgdGhlIEJJQyBjb21wYXJpc29uLgoKYGBge3J9CmV6X3QxX2ZhXzEzID0gZmEocmVzX2NsZWFuX3Rlc3RfZGF0YV9lel81MjJfY29uZGl0aW9uLCBlZmFfZXpfdDFfY29tcF9tZXRyaWNzJGNvbXBbMV0sIHJvdGF0ZT0nb2JsaW1pbicsIGZtPSdtaW5yZXMnLCBzY29yZXM9J3RlbkJlcmdlJykKYGBgCgpGaXQgdGhlIDMgZmFjdG9yIG1vZGVsIHRoYXQgaXMgb2YgdGhlb3JldGljYWwgaW50ZXJlc3QuCgpgYGB7cn0KZXpfdDFfZmFfMyA9IGZhKHJlc19jbGVhbl90ZXN0X2RhdGFfZXpfNTIyX2NvbmRpdGlvbiwgMywgcm90YXRlPSdvYmxpbWluJywgZm09J21pbnJlcycsIHNjb3Jlcz0ndGVuQmVyZ2UnKQpgYGAKCkRpZmZlcmVuY2VzIGluIEJJQydzIGFyZSBkaWZmaWN1bHQgdG8gaW50ZXJwcmV0LgoKSSB0cmllZCB0byBydW4gYSBtb3JlIGZvcm1hbCBtb2RlbCBjb21wYXJpc29uIGJldHdlZW4gdGhlIDMgZmFjdG9yIHNvbHV0aW9uIHRoYXQgd2UgaGF2ZSB0aGVvcmV0aWNhbCByZWFzb25zIGZvciBhbmQgdGhlIDE2IGZhY3RvciBtb2RlbCB0aGF0IGlzIHNlbGVjdGVkIGJhc2VkIG9uIEJJQy4gQmVsb3cgaXMgdGhlIHJlc3VsdC4gSSdtIG5vdCBxdWlldCBzdXJlIGhvdyB0byBpbnRlcnByZXRlZCBidXQgaXQgc2VlbXMgdG8gc3VnZ2VzdCB0aGF0IHRoZSBtb3JlIGNvbXBsaWNhdGVkIG1vZGVsIGlzIG5vdCBzaWduaWZpY2FudGx5IGJldHRlci4KCmBgYHtyfQphbm92YShlel90MV9mYV8zLCBlel90MV9mYV8xMykKYGBgCgpTbyB3aGF0IHdvdWxkIHRoZSAzIGZhY3RvciBzb2x1dGlvbiBsb29rIGxpa2U/IE5vdCBzdXJwcmlzaW5nbHksIHZlcnkgc2ltaWxhciB0byB0aGUgUEMncy4KCmBgYHtyIG1lc3NhZ2U9RkFMU0V9CmV6X3QxX2ZhXzNfbG9hZGluZ3MgPSBhcy5kYXRhLmZyYW1lKGV6X3QxX2ZhXzMkbG9hZGluZ3NbXSkKCmV6X3QxX2ZhXzNfbG9hZGluZ3NbYWJzKGV6X3QxX2ZhXzNfbG9hZGluZ3MpPDAuM109TkEKCnRtcCA9IGV6X3QxX2ZhXzNfbG9hZGluZ3MgJT4lCiAgbXV0YXRlKGR2ID0gcm93Lm5hbWVzKC4pKSAlPiUKICBzZWxlY3QoZHYsIE1SMSwgTVIyLCBNUjMpICU+JQogIG11dGF0ZShudW1fbG9hZGluZyA9IDMtKGlzLm5hKE1SMSkraXMubmEoTVIyKStpcy5uYShNUjMpICkgKSAlPiUKICBmaWx0ZXIobnVtX2xvYWRpbmchPTApICU+JQogIHNlbGVjdCgtbnVtX2xvYWRpbmcpICU+JQogIGFycmFuZ2UoLU1SMSwgLU1SMiwgLU1SMykgJT4lCiAgbXV0YXRlKG9yZGVyX251bSA9IDE6bigpLAogICAgICAgICBkdiA9IHJlb3JkZXIoZHYsIC1vcmRlcl9udW0pKSAlPiUKICBzZWxlY3QoLW9yZGVyX251bSkgJT4lCiAgZ2F0aGVyKEZhY3RvciwgTG9hZGluZywgLWR2KSAlPiUKICBuYS5leGNsdWRlKCkgJT4lCiAgbXV0YXRlKG5lZ19sb2FkID0gZmFjdG9yKGlmZWxzZShMb2FkaW5nPjAsIk5BIiwiIzAwMDAwMCIpKSwKICAgICAgICAgdmFyX3R5cGUgPSBpZmVsc2UoZ3JlcGwoImRyaWZ0IiwgZHYpLCAiRHJpZnQgcmF0ZSIsCiAgICAgICAgICAgICAgICAgIGlmZWxzZShncmVwbCgidGhyZXNoIiwgZHYpLCAiVGhyZXNob2xkIiwKICAgICAgICAgICAgICAgICAgICAgICAgIGlmZWxzZShncmVwbCgibm9uX2RlYyIsIGR2KSwgIk5vbi1kZWNpc2lvbiIsIE5BKSkpLAogICAgICAgICAgICAgICAgICAgICAgICAgICB2YXJfdHlwZSA9IGZhY3Rvcih2YXJfdHlwZSwgbGV2ZWxzPWMoIkRyaWZ0IHJhdGUiLCAiVGhyZXNob2xkIiwgIk5vbi1kZWNpc2lvbiIpKSkKCgpwID0gdG1wJT4lCiAgZ2dwbG90KGFlcyhkdiwgYWJzKExvYWRpbmcpLCBmaWxsPXZhcl90eXBlLCBjb2wgPSBuZWdfbG9hZCkpKwogIGdlb21fYmFyKHN0YXQgPSAiaWRlbnRpdHkiLCBhbHBoYT0wLjUpKwogIGZhY2V0X3dyYXAofkZhY3RvciwgbnJvdz0xKSsKICBjb29yZF9mbGlwKCkrCiAgeGxhYigiIikrCiAgeWxhYigiQWJzb2x1dGUgTG9hZGluZyIpKwogIHNjYWxlX2NvbG9yX2lkZW50aXR5KCkrCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gImJvdHRvbSIsCiAgICAgICAgbGVnZW5kLnRpdGxlPWVsZW1lbnRfYmxhbmsoKSkKICAgICAgICAjIGF4aXMudGV4dC55ID0gZWxlbWVudF9ibGFuaygpKQoKZ2dzYXZlKCdFWl9GQV9UMV8zX3dfbGFiZWxzLmpwZWcnLHBsb3Q9cCwgZGV2aWNlID0gJ2pwZWcnLCBwYXRoID0gZmlnX3BhdGgsIHdpZHRoID0gMTUsIGhlaWdodCA9IDE1LCB1bml0cyA9ICJpbiIpCgpwID0gdG1wJT4lCiAgZ2dwbG90KGFlcyhkdiwgYWJzKExvYWRpbmcpLCBmaWxsPXZhcl90eXBlLCBjb2wgPSBuZWdfbG9hZCkpKwogIGdlb21fYmFyKHN0YXQgPSAiaWRlbnRpdHkiLCBhbHBoYT0wLjUpKwogIGZhY2V0X3dyYXAofkZhY3RvciwgbnJvdz0xKSsKICBjb29yZF9mbGlwKCkrCiAgeGxhYigiIikrCiAgeWxhYigiQWJzb2x1dGUgTG9hZGluZyIpKwogIHNjYWxlX2NvbG9yX2lkZW50aXR5KCkrCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gImJvdHRvbSIsCiAgICAgICAgbGVnZW5kLnRpdGxlPWVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRleHQueSA9IGVsZW1lbnRfYmxhbmsoKSkKCmdnc2F2ZSgnRVpfRkFfVDFfMy5qcGVnJyxwbG90PXAsIGRldmljZSA9ICdqcGVnJywgcGF0aCA9IGZpZ19wYXRoLCB3aWR0aCA9IDEwLCBoZWlnaHQgPSAxMiwgdW5pdHMgPSAiaW4iKQoKcApgYGAKCgojIyMgUENzIHZzIEZhY3RvcnMKClZhcmlhbmNlIGFjY29udGVkIGZvciBieSBQQ0EgYW5kIEVGQQoKYGBge3J9CmV6X3QxX3BjYV8zJFZhY2NvdW50ZWQKYGBgCgpgYGB7cn0KZXpfdDFfZmFfMyRWYWNjb3VudGVkCmBgYAoKQ29tcGFyaXNvbiBvZiBsb2FkaW5ncwoKYGBge3Igd2FybmluZz1GQUxTRSwgbWVzc2FnZT1GQUxTRX0KZXpfdDFfcGNhXzNfbG9hZGluZ3MgJT4lCiAgbXV0YXRlKGR2ID0gcm93Lm5hbWVzKC4pKSAlPiUKICBsZWZ0X2pvaW4oZXpfdDFfZmFfM19sb2FkaW5ncyAlPiUKICAgICAgICAgICAgICBtdXRhdGUoZHYgPSByb3cubmFtZXMoLikpLCBieT0iZHYiKSAlPiUKICBnYXRoZXIoa2V5LCB2YWx1ZSwgLWR2KSAlPiUKICBtdXRhdGUoZmFjdG9yX251bSA9IGlmZWxzZShncmVwbCgiMSIsIGtleSksIDEsIGlmZWxzZShncmVwbCgiMiIsIGtleSksIDIsIGlmZWxzZShncmVwbCgiMyIsIGtleSksIDMsIE5BKSkpLAogICAgICAgICBrZXkgPSBpZmVsc2UoZ3JlcGwoIlRDIiwga2V5KSwgIlBDQSIsIGlmZWxzZShncmVwbCgiTVIiLCBrZXkpLCAiRUZBIiwgTkEpKSkgJT4lIHNwcmVhZChrZXksIHZhbHVlKSAlPiUKICBnZ3Bsb3QoYWVzKFBDQSwgRUZBLCBjb2w9ZmFjdG9yKGZhY3Rvcl9udW0pKSkrCiAgZ2VvbV9wb2ludCgpKwogIGdlb21fYWJsaW5lKGFlcyhpbnRlcmNlcHQ9MCwgc2xvcGUgPSAxKSwgbGluZXR5cGU9MikrCiAgZmFjZXRfd3JhcCh+ZmFjdG9yX251bSkrCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiLAogICAgICAgIHBhbmVsLmdyaWQgPSBlbGVtZW50X2JsYW5rKCkpCmBgYAoKCiMjIyBBcnRpZmljYWwgUENBCgpgYGB7cn0KYXJ0X3BjYV9zY29yZXMgPSByZXNfY2xlYW5fdGVzdF9kYXRhX2V6XzUyMl9jb25kaXRpb24gJT4lCiAgbXV0YXRlKHN1Yl9pZCA9IHRlc3RfZGF0YV81MjIkc3ViX2lkKSAlPiUKICBnYXRoZXIoZHYsIHZhbHVlLCAtc3ViX2lkKSAlPiUKICBsZWZ0X2pvaW4obWVhc3VyZV9sYWJlbHMgJT4lIHNlbGVjdChkdiwgcnRfYWNjKSwgYnk9ImR2IikgJT4lCiAgZ3JvdXBfYnkoc3ViX2lkLCBydF9hY2MpICU+JQogIHN1bW1hcmlzZShtZWFuX3ZhbCA9IG1lYW4odmFsdWUpKSAlPiUKICBtdXRhdGUocnRfYWNjID0gaWZlbHNlKHJ0X2FjYyA9PSAiZHJpZnQgcmF0ZSIsICJNUjEiLCBpZmVsc2UocnRfYWNjID09ICJub24tZGVjaXNpb24iLCAiTVIyIiwgIk1SMyIpKSkKCmFydF9wY2Ffc2NvcmVzCmBgYAoKCmBgYHtyfQpkYXRhLmZyYW1lKGV6X3QxX2ZhXzMkc2NvcmVzKSAlPiUKICAgbXV0YXRlKHN1Yl9pZCA9IHRlc3RfZGF0YV81MjIkc3ViX2lkKSAlPiUKICBnYXRoZXIocnRfYWNjLCBmYWN0b3Jfc2NvcmUsIC1zdWJfaWQpJT4lCiAgbGVmdF9qb2luKGFydF9wY2Ffc2NvcmVzLCBieT1jKCJzdWJfaWQiLCAicnRfYWNjIikpICU+JQogIGdncGxvdChhZXMobWVhbl92YWwsZmFjdG9yX3Njb3JlLCBjb2w9cnRfYWNjKSkrCiAgZ2VvbV9wb2ludCgpKwogIGdlb21fYWJsaW5lKGFlcyhpbnRlcmNlcHQ9MCwgc2xvcGU9MSksIGxpbmV0eXBlPTIpKwogIGZhY2V0X3dyYXAofnJ0X2FjYywgc2NhbGVzPSJmcmVlIikrCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiLAogICAgICAgIHBhbmVsLmdyaWQgPSBlbGVtZW50X2JsYW5rKCkpKwogIHhsYWIoIk1lYW4gdmFsdWUgb2YgaW5kaXZpZHVhbCBtZWFzdXJlcyIpKwogIHlsYWIoIkVGQSIpCmBgYAoKKipCYXNlZCBvbiB0aGVzZSByZXN1bHRzIEkgcmFuIDMgZmFjdG9yIEVGQSdzIG9uIHRoZSByZW1haW5pbmcgZGF0YS4qKgoKIyMgRUZBIG9uIEhERE0gdmFyaWFibGVzIG9mIHRlc3QgZGF0YQoKRXh0cmFjdCBFWiB2YXJpYWJsZXMKCmBgYHtyfQp0ZXN0X2RhdGFfaGRkbSA9IHRlc3RfZGF0YSAlPiUKICBzZWxlY3QoZ3JlcCgnaGRkbScsIG5hbWVzKHRlc3RfZGF0YSksIHZhbHVlPVQpKQpgYGAKClJlbW92ZSB2YXJpYWJsZXMgdGhhdCBhcmUgY29ycmVsYXRlZCA+MC44NQoKYGBge3J9CmNsZWFuX3Rlc3RfZGF0YV9oZGRtID0gcmVtb3ZlX2NvcnJlbGF0ZWRfdGFza192YXJpYWJsZXModGVzdF9kYXRhX2hkZG0pCmBgYAoKUmVtb3ZlIG91dGxpZXJzICg+Mi41IFNEIGF3YXkpCgpgYGB7cn0KY2xlYW5fdGVzdF9kYXRhX2hkZG0gPSBhcy5kYXRhLmZyYW1lKGFwcGx5KGNsZWFuX3Rlc3RfZGF0YV9oZGRtLCAyLCByZW1vdmVfb3V0bGllcnMpKQpgYGAKClRyYW5zZm9ybSBza2V3ZWQgdmFyaWFibGVzCgpgYGB7cn0KI05vdGhpbmcgdG8gdHJhbnNmb3JtCm51bWVyaWNfY29scyA9IGdldF9udW1lcmljX2NvbHMoKQpudW1lcmljX2NvbHMgPSBudW1lcmljX2NvbHNbbnVtZXJpY19jb2xzICVpbiUgbmFtZXMoY2xlYW5fdGVzdF9kYXRhX2hkZG0pID09IFRdCmNsZWFuX3Rlc3RfZGF0YV9oZGRtID0gdHJhbnNmb3JtX3JlbW92ZV9za2V3KGNsZWFuX3Rlc3RfZGF0YV9oZGRtLCBudW1lcmljX2NvbHMpCmBgYAoKRHJvcCBzdWJqZWN0IGlkZW50aWZpZXIgY29sdW1uLCBtZWFuIGltcHV0ZSBhbmQgZHJvcCBjb2xzIHdpdGggbm8gdmFyaWFuY2UKCmBgYHtyfQpjbGVhbl90ZXN0X2RhdGFfaGRkbV9zdGQgPSBjbGVhbl90ZXN0X2RhdGFfaGRkbSAlPiUgbXV0YXRlX2lmKGlzLm51bWVyaWMsIHNjYWxlKQoKI21lYW4gaW1wdXRhdGlvbgpjbGVhbl90ZXN0X2RhdGFfaGRkbV9zdGRbaXMubmEoY2xlYW5fdGVzdF9kYXRhX2hkZG1fc3RkKV09MAoKI2Ryb3AgY29scyB3aXRoIG5vIHZhcmlhbmNlCmNsZWFuX3Rlc3RfZGF0YV9oZGRtX3N0ZCA9IGNsZWFuX3Rlc3RfZGF0YV9oZGRtX3N0ZCAlPiUKICBzZWxlY3RfaWYoZnVuY3Rpb24oY29sKSBzZChjb2wpICE9IDApCmBgYAoKUmVzaWR1YWxpemUgQWdlIGFuZCBTZXggZWZmZWN0cwoKYGBge3Igd2FybmluZz1GQUxTRSwgbWVzc2FnZT1GQUxTRX0KY2xlYW5fdGVzdF9kYXRhX2hkZG1fc3RkID0gY2JpbmQoY2xlYW5fdGVzdF9kYXRhX2hkZG1fc3RkLCBkZW1vZ3JhcGhpY3NbLGMoIkFnZSIsICJTZXgiKV0pCgpyZXNfY2xlYW5fdGVzdF9kYXRhX2hkZG0gPSByZXNpZHVhbGl6ZV9iYXNlbGluZShjbGVhbl90ZXN0X2RhdGFfaGRkbV9zdGQpCmBgYAoKRml0IHRoZSAzIGZhY3RvciBtb2RlbCB0aGF0IGlzIG9mIHRoZW9yZXRpY2FsIGludGVyZXN0LgoKYGBge3J9CmhkZG1fdDFfZmFfMyA9IGZhKHJlc19jbGVhbl90ZXN0X2RhdGFfaGRkbSwgMywgcm90YXRlPSdvYmxpbWluJywgZm09J21pbnJlcycsIHNjb3Jlcz0ndGVuQmVyZ2UnKQpgYGAKClRoZSB0aHJlZSBmYWN0b3IgbW9kZWwgZG9lcyBub3Qgd29yayBhcyB3ZWxsIGZvciBIRERNJ3MgYmVjYXVzZSBvZiB0aGUgMTA2IHZhcmlhYmxlcyB3ZSBoYXZlIDc1IGFyZSBkcmlmdCByYXRlcywgMTcgYXJlIHRocmVzaG9sZCBhbmQgMTQgYXJlIG5vbi1kZWNpc2lvbiB0aW1lcy4gVGhpcyBpcyBiZWNhdXNlIHdoaWxlIGFsbCB0aGUgcGFyYW1ldGVycyBhcmUgZml0IHNlcGFyYXRlbHkgZm9yIEVaIG9ubHkgdGhlIGRyaWZ0IHJhdGUgcGFyYW1ldGVyIGlzIGFsbG93ZWQgdG8gdmFyeSBieSBjb25kaXRpb24gZm9yIHRoZSBIRERNLiBTbyB0aGUgbW9kZWwgaXMgbW9zdGx5IHNlcGFyYXRpbmcgb3V0IGRyaWZ0IHJhdGVzIGZyb20gZWFjaCBvdGhlci4KCmBgYHtyIG1lc3NhZ2U9RkFMU0V9CmhkZG1fdDFfZmFfM19sb2FkaW5ncyA9IGFzLmRhdGEuZnJhbWUoaGRkbV90MV9mYV8zJGxvYWRpbmdzW10pCgpoZGRtX3QxX2ZhXzNfbG9hZGluZ3NbYWJzKGhkZG1fdDFfZmFfM19sb2FkaW5ncyk8MC4zXT1OQQoKdG1wID0gaGRkbV90MV9mYV8zX2xvYWRpbmdzICU+JQogIG11dGF0ZShkdiA9IHJvdy5uYW1lcyguKSkgJT4lCiAgc2VsZWN0KGR2LCBNUjEsIE1SMiwgTVIzKSAlPiUKICBtdXRhdGUobnVtX2xvYWRpbmcgPSAzLShpcy5uYShNUjEpK2lzLm5hKE1SMikraXMubmEoTVIzKSApICkgJT4lCiAgZmlsdGVyKG51bV9sb2FkaW5nIT0wKSAlPiUKICBzZWxlY3QoLW51bV9sb2FkaW5nKSAlPiUKICBhcnJhbmdlKC1NUjEsIC1NUjIsIC1NUjMpICU+JQogIG11dGF0ZShvcmRlcl9udW0gPSAxOm4oKSwKICAgICAgICAgZHYgPSByZW9yZGVyKGR2LCAtb3JkZXJfbnVtKSkgJT4lCiAgc2VsZWN0KC1vcmRlcl9udW0pICU+JQogIGdhdGhlcihGYWN0b3IsIExvYWRpbmcsIC1kdikgJT4lCiAgbmEuZXhjbHVkZSgpICU+JQogIG11dGF0ZShuZWdfbG9hZCA9IGZhY3RvcihpZmVsc2UoTG9hZGluZz4wLCJOQSIsIiMwMDAwMDAiKSksCiAgICAgICAgIHZhcl90eXBlID0gaWZlbHNlKGdyZXBsKCJkcmlmdCIsIGR2KSwgImRyaWYgcmF0ZSIsCiAgICAgICAgICAgICAgICAgIGlmZWxzZShncmVwbCgidGhyZXNoIiwgZHYpLCAidGhyZXNob2xkIiwKICAgICAgICAgICAgICAgICAgICAgICAgIGlmZWxzZShncmVwbCgibm9uX2RlYyIsIGR2KSwgIm5vbi1kZWNpc2lvbiIsIE5BKSkpKQoKCnAgPSB0bXAlPiUKICBnZ3Bsb3QoYWVzKGR2LCBhYnMoTG9hZGluZyksIGZpbGw9dmFyX3R5cGUsIGNvbCA9IG5lZ19sb2FkKSkrCiAgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIsIGFscGhhPTAuNSkrCiAgZmFjZXRfd3JhcCh+RmFjdG9yLCBucm93PTEpKwogIGNvb3JkX2ZsaXAoKSsKICB4bGFiKCIiKSsKICB5bGFiKCJBYnNvbHV0ZSBMb2FkaW5nIikrCiAgc2NhbGVfY29sb3JfaWRlbnRpdHkoKSsKICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAiYm90dG9tIiwKICAgICAgICBsZWdlbmQudGl0bGU9ZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGV4dC55ID0gZWxlbWVudF9ibGFuaygpKQoKZ2dzYXZlKCdIRERNX1QxX0ZBXzMuanBlZycscGxvdD1wLCBkZXZpY2UgPSAnanBlZycsIHBhdGggPSBmaWdfcGF0aCwgd2lkdGggPSAxMCwgaGVpZ2h0ID0gMTIsIHVuaXRzID0gImluIikKCnAKYGBgCgpEb2VzIGl0IGZpdCBiZXR0ZXIgaWYgSSB1c2UganVzdCB0aGUgbWFpbiBkcmlmdCByYXRlcyBhbmQgbm90IHRoZSBjb25kaXRpb24gb25lcz8KCmBgYHtyfQpoZGRtX3Zhcl9zdWJzZXQgPSBjKCJhZGFwdGl2ZV9uX2JhY2suaGRkbV9kcmlmdCIgLCAiYXR0ZW50aW9uX25ldHdvcmtfdGFzay5oZGRtX2RyaWZ0IiwgImNob2ljZV9yZWFjdGlvbl90aW1lLmhkZG1fZHJpZnQiLCAiZGlyZWN0ZWRfZm9yZ2V0dGluZy5oZGRtX2RyaWZ0IiwgImRvdF9wYXR0ZXJuX2V4cGVjdGFuY3kuaGRkbV9kcmlmdCIgLCAibG9jYWxfZ2xvYmFsX2xldHRlci5oZGRtX2RyaWZ0IiwgIm1vdG9yX3NlbGVjdGl2ZV9zdG9wX3NpZ25hbC5oZGRtX2RyaWZ0IiwgICJyZWNlbnRfcHJvYmVzLmhkZG1fZHJpZnQiLCAic2hhcGVfbWF0Y2hpbmcuaGRkbV9kcmlmdCIgLCAic2ltb24uaGRkbV9kcmlmdCIsICJzdGltX3NlbGVjdGl2ZV9zdG9wX3NpZ25hbC5oZGRtX2RyaWZ0IiwgInN0b3Bfc2lnbmFsLmhkZG1fZHJpZnQiLCAic3Ryb29wLmhkZG1fZHJpZnQiICwgInRocmVlYnl0d28uaGRkbV9kcmlmdCIsICJhZGFwdGl2ZV9uX2JhY2suaGRkbV90aHJlc2giLCAiYXR0ZW50aW9uX25ldHdvcmtfdGFzay5oZGRtX3RocmVzaCIsICJjaG9pY2VfcmVhY3Rpb25fdGltZS5oZGRtX3RocmVzaCIsICJkaXJlY3RlZF9mb3JnZXR0aW5nLmhkZG1fdGhyZXNoIiwgImRvdF9wYXR0ZXJuX2V4cGVjdGFuY3kuaGRkbV90aHJlc2gubG9nVHIiLCAibG9jYWxfZ2xvYmFsX2xldHRlci5oZGRtX3RocmVzaCIsICJtb3Rvcl9zZWxlY3RpdmVfc3RvcF9zaWduYWwuaGRkbV90aHJlc2giLCAicmVjZW50X3Byb2Jlcy5oZGRtX3RocmVzaCIsICJzaGFwZV9tYXRjaGluZy5oZGRtX3RocmVzaCIsICJzaW1vbi5oZGRtX3RocmVzaCIsICJzdGltX3NlbGVjdGl2ZV9zdG9wX3NpZ25hbC5oZGRtX3RocmVzaC5sb2dUciIsICJzdG9wX3NpZ25hbC5oZGRtX3RocmVzaCIsICJzdG9wX3NpZ25hbC5oZGRtX3RocmVzaF9oaWdoIiwgInN0b3Bfc2lnbmFsLmhkZG1fdGhyZXNoX2xvdyIsICJzdG9wX3NpZ25hbC5wcm9hY3RpdmVfc2xvd2luZ19oZGRtX3RocmVzaCIsICJzdHJvb3AuaGRkbV90aHJlc2giLCAidGhyZWVieXR3by5oZGRtX3RocmVzaCIsIGdyZXAoIm5vbl9kZWMiLCBuYW1lcyh0ZXN0X2RhdGFfaGRkbSksIHZhbHVlID0gVCkpCgpyZXNfY2xlYW5fdGVzdF9kYXRhX2hkZG1fc3Vic2V0ID0gcmVzX2NsZWFuX3Rlc3RfZGF0YV9oZGRtICU+JSBzZWxlY3QoaGRkbV92YXJfc3Vic2V0KQpgYGAKCmBgYHtyfQpoZGRtX3QxX2ZhXzNfc3Vic2V0ID0gZmEocmVzX2NsZWFuX3Rlc3RfZGF0YV9oZGRtX3N1YnNldCwgMywgcm90YXRlPSdvYmxpbWluJywgZm09J21pbnJlcycsIHNjb3Jlcz0ndGVuQmVyZ2UnKQpgYGAKClRoZW4gaXQgd29ya3MgYmV0dGVyIGJ1dCBzdGlsbCBub3QgZG9pbmcgYSBncmVhdCBqb2Igc2VwYXJhdGluZyBvdXQgdGhyZXNob2xkcyBmcm9tIG5vbi1kZWNpc2lvbiB0aW1lcy4KCmBgYHtyIG1lc3NhZ2U9RkFMU0V9CmhkZG1fdDFfZmFfM19zdWJzZXRfbG9hZGluZ3MgPSBhcy5kYXRhLmZyYW1lKGhkZG1fdDFfZmFfM19zdWJzZXQkbG9hZGluZ3NbXSkKCmhkZG1fdDFfZmFfM19zdWJzZXRfbG9hZGluZ3NbYWJzKGhkZG1fdDFfZmFfM19zdWJzZXRfbG9hZGluZ3MpPDAuM109TkEKCnRtcCA9IGhkZG1fdDFfZmFfM19zdWJzZXRfbG9hZGluZ3MgJT4lCiAgbXV0YXRlKGR2ID0gcm93Lm5hbWVzKC4pKSAlPiUKICBzZWxlY3QoZHYsIE1SMSwgTVIyLCBNUjMpICU+JQogIG11dGF0ZShudW1fbG9hZGluZyA9IDMtKGlzLm5hKE1SMSkraXMubmEoTVIyKStpcy5uYShNUjMpICkgKSAlPiUKICBmaWx0ZXIobnVtX2xvYWRpbmchPTApICU+JQogIHNlbGVjdCgtbnVtX2xvYWRpbmcpICU+JQogIGFycmFuZ2UoLU1SMSwgLU1SMiwgLU1SMykgJT4lCiAgbXV0YXRlKG9yZGVyX251bSA9IDE6bigpLAogICAgICAgICBkdiA9IHJlb3JkZXIoZHYsIC1vcmRlcl9udW0pKSAlPiUKICBzZWxlY3QoLW9yZGVyX251bSkgJT4lCiAgZ2F0aGVyKEZhY3RvciwgTG9hZGluZywgLWR2KSAlPiUKICBuYS5leGNsdWRlKCkgJT4lCiAgbXV0YXRlKG5lZ19sb2FkID0gZmFjdG9yKGlmZWxzZShMb2FkaW5nPjAsIk5BIiwiIzAwMDAwMCIpKSwKICAgICAgICAgdmFyX3R5cGUgPSBpZmVsc2UoZ3JlcGwoImRyaWZ0IiwgZHYpLCAiZHJpZiByYXRlIiwKICAgICAgICAgICAgICAgICAgaWZlbHNlKGdyZXBsKCJ0aHJlc2giLCBkdiksICJ0aHJlc2hvbGQiLAogICAgICAgICAgICAgICAgICAgICAgICAgaWZlbHNlKGdyZXBsKCJub25fZGVjIiwgZHYpLCAibm9uLWRlY2lzaW9uIiwgTkEpKSkpCgoKcCA9IHRtcCU+JQogIGdncGxvdChhZXMoZHYsIGFicyhMb2FkaW5nKSwgZmlsbD12YXJfdHlwZSwgY29sID0gbmVnX2xvYWQpKSsKICBnZW9tX2JhcihzdGF0ID0gImlkZW50aXR5IiwgYWxwaGE9MC41KSsKICBmYWNldF93cmFwKH5GYWN0b3IsIG5yb3c9MSkrCiAgY29vcmRfZmxpcCgpKwogIHhsYWIoIiIpKwogIHlsYWIoIkFic29sdXRlIExvYWRpbmciKSsKICBzY2FsZV9jb2xvcl9pZGVudGl0eSgpKwogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJib3R0b20iLAogICAgICAgIGxlZ2VuZC50aXRsZT1lbGVtZW50X2JsYW5rKCkpCiAgICAgICAgI2F4aXMudGV4dC55ID0gZWxlbWVudF9ibGFuaygpKQoKZ2dzYXZlKCdIRERNX1QxX0ZBXzNfc3Vic2V0LmpwZWcnLHBsb3Q9cCwgZGV2aWNlID0gJ2pwZWcnLCBwYXRoID0gZmlnX3BhdGgsIHdpZHRoID0gMTAsIGhlaWdodCA9IDEyLCB1bml0cyA9ICJpbiIpCmBgYAoKYGBge3J9CmtuaXRyOjppbmNsdWRlX2dyYXBoaWNzKHBhc3RlMChmaWdfcGF0aCwgJ0hERE1fVDFfRkFfM19zdWJzZXQuanBlZycpKQpgYGAKCiMjIEVGQSBvbiBFWiB2YXJpYWJsZXMgb2YgcmV0ZXN0IGRhdGEKCiMjIyBQcmVwYXJpbmcgdGhlIGRhdGEKCmBgYHtyfQpyZXRlc3RfZGF0YV9leiA9IHJldGVzdF9kYXRhICU+JQogIHNlbGVjdChncmVwKCdFWicsIG5hbWVzKHJldGVzdF9kYXRhKSwgdmFsdWU9VCkpCmBgYAoKUmVtb3ZlIHZhcmlhYmxlcyB0aGF0IGFyZSBjb3JyZWxhdGVkID4wLjg1CgpgYGB7cn0KY2xlYW5fcmV0ZXN0X2RhdGFfZXogPSByZW1vdmVfY29ycmVsYXRlZF90YXNrX3ZhcmlhYmxlcyhyZXRlc3RfZGF0YV9leikKYGBgCgpSZW1vdmUgb3V0bGllcnMgKD4yLjUgU0QgYXdheSkKCmBgYHtyfQpjbGVhbl9yZXRlc3RfZGF0YV9leiA9IGFzLmRhdGEuZnJhbWUoYXBwbHkoY2xlYW5fcmV0ZXN0X2RhdGFfZXosIDIsIHJlbW92ZV9vdXRsaWVycykpCmBgYAoKVHJhbnNmb3JtIHNrZXdlZCB2YXJpYWJsZXMKCmBgYHtyfQojTm90aGluZyB0byB0cmFuc2Zvcm0KbnVtZXJpY19jb2xzID0gZ2V0X251bWVyaWNfY29scygpCm51bWVyaWNfY29scyA9IG51bWVyaWNfY29sc1tudW1lcmljX2NvbHMgJWluJSBuYW1lcyhjbGVhbl9yZXRlc3RfZGF0YV9leikgPT0gVF0KY2xlYW5fcmV0ZXN0X2RhdGFfZXogPSB0cmFuc2Zvcm1fcmVtb3ZlX3NrZXcoY2xlYW5fcmV0ZXN0X2RhdGFfZXosIG51bWVyaWNfY29scykKYGBgCgpEcm9wIHN1YmplY3QgaWRlbnRpZmllciBjb2x1bW4sIG1lYW4gaW1wdXRlIGFuZCBkcm9wIGNvbHMgd2l0aCBubyB2YXJpYW5jZQoKYGBge3J9CmNsZWFuX3JldGVzdF9kYXRhX2V6X3N0ZCA9IGNsZWFuX3JldGVzdF9kYXRhX2V6ICU+JSBtdXRhdGVfaWYoaXMubnVtZXJpYywgc2NhbGUpCgojbWVhbiBpbXB1dGF0aW9uCmNsZWFuX3JldGVzdF9kYXRhX2V6X3N0ZFtpcy5uYShjbGVhbl9yZXRlc3RfZGF0YV9lel9zdGQpXT0wCgojZHJvcCBjb2xzIHdpdGggbm8gdmFyaWFuY2UKY2xlYW5fcmV0ZXN0X2RhdGFfZXpfc3RkID0gY2xlYW5fcmV0ZXN0X2RhdGFfZXpfc3RkICU+JQogIHNlbGVjdF9pZihmdW5jdGlvbihjb2wpIHNkKGNvbCkgIT0gMCkKYGBgCgpSZXNpZHVhbGl6ZSBBZ2UgYW5kIFNleCBlZmZlY3RzCgpgYGB7ciB3YXJuaW5nPUZBTFNFLCBtZXNzYWdlPUZBTFNFfQpjbGVhbl9yZXRlc3RfZGF0YV9lel9zdGQgPSBjYmluZChjbGVhbl9yZXRlc3RfZGF0YV9lel9zdGQsIGRlbW9ncmFwaGljc1ssYygiQWdlIiwgIlNleCIpXSkKCnJlc19jbGVhbl9yZXRlc3RfZGF0YV9leiA9IHJlc2lkdWFsaXplX2Jhc2VsaW5lKGNsZWFuX3JldGVzdF9kYXRhX2V6X3N0ZCkKYGBgCgpUaGVyZSBhcmUgdHdvIHdheXMgd2UgY2FuIGdvIGFib3V0IHRoaXM6CgoxLiBQcmVkaWN0aW5nIGZhY3RvciBzY29yZXMgZm9yIFQyIGRhdGEgdXNpbmcgdGhlIFQxIGZhY3RvciBzb2x1dGlvbgoyLiBGaXR0aW5nIGEgbmV3IDMtZmFjdG9yIG1vZGVsIHRvIHRoZSBUMiBkYXRhCgojIyMgUHJlZGljdCByZXRlc3QgZGF0YSB1c2luZyB0aGUgMyBmYWN0b3IgRUZBIGZyb20gdGVzdCBkYXRhCgpEZWZpbmUgaGVscGVyIGZ1bmN0aW9ucyB0byBwcmVkaWN0IHQyIGZhY3RvciBzY29yZXMgdXNpbmcgZGlmZmVyZW50IHNjb3JpbmcgbWV0aG9kcyBhbmQgY2FsY3VsYXRpbmcgdGhlaXIgSUNDcyB3aXRoIHQxIGZhY3RvciBzY29yZXMuCgpgYGB7cn0KcHJlZGljdF90Ml9mYV9zY29yZXMgPSBmdW5jdGlvbih0MV9kZiA9IHJlc19jbGVhbl90ZXN0X2RhdGFfZXosIHQyX2RmID0gcmVzX2NsZWFuX3JldGVzdF9kYXRhX2V6LCBzY29yZXM9InRlbkJlcmdlIiwgbmZhY3RvcnMgPSAzLHJvdGF0ZT0nb2JsaW1pbicsZm09J21pbnJlcycsIHN1Yl9pZHMgPSByZXRlc3RfZGF0YSRzdWJfaWQpewogIHJlcXVpcmUocHN5Y2gpCiAgcmVxdWlyZSh0aWR5dmVyc2UpCiAgdDFfZmEgPSBmYSh0MV9kZiwgbmZhY3RvcnMsIHJvdGF0ZT1yb3RhdGUsIGZtPWZtLCBzY29yZXM9c2NvcmVzKQogIHQxX2ZhX3Njb3JlcyA9IGRhdGEuZnJhbWUodDFfZmEkc2NvcmVzKSAlPiUKICAgIG11dGF0ZShzdWJfaWQgPSBzdWJfaWRzKQoKICB0Ml9wcmVkID0gcHJlZGljdCh0MV9mYSwgdDJfZGYpCiAgdDJfcHJlZF9zY29yZXMgPSBhcy5kYXRhLmZyYW1lKHQyX3ByZWQpICU+JQogICAgbXV0YXRlKHN1Yl9pZCA9IHN1Yl9pZHMpCiAgcmV0dXJuKGxpc3QodDFfZmFfc2NvcmVzPXQxX2ZhX3Njb3JlcywgdDJfcHJlZF9zY29yZXM9dDJfcHJlZF9zY29yZXMpKQp9CgpnZXRfaWNjX2Zvcl9zY29yZV90eXBlID0gZnVuY3Rpb24oc2NvcmVzKXsKICBwID0gcHJlZGljdF90Ml9mYV9zY29yZXMoc2NvcmVzPXNjb3JlcykKICByX2RmID0gbWFrZV9yZWxfZGYocCR0MV9mYV9zY29yZXMsIHAkdDJfcHJlZF9zY29yZXMsIG1ldHJpY3MgPSBjKCJpY2MyLjEiKSkKICByX2RmID0gcl9kZiAlPiUKICAgIHNwcmVhZChkdixpY2MyLjEpICU+JQogICAgbXV0YXRlKHNjb3JlcyA9IHNjb3JlcykKICByZXR1cm4ocl9kZikKfQpgYGAKClVzaW5nIGB0ZW5CZXJnZWAgb3VyIGRlZmF1bHQgb3B0aW9uIHRvIGNhbGN1bGF0ZSBmYWN0b3Igc2NvcmVzCgpgYGB7cn0KZXpfdDJfZmFfM19wcmVkID0gcHJlZGljdF90Ml9mYV9zY29yZXMoKQpgYGAKClBsb3R0aW5nIHByZWRpY3RlZCBgdGVuQmVyZ2VgIHNjb3JlcyBmb3IgVDIgZGF0YSB1c2luZyBUMSBGQSBhZ2FpbnN0IFQxIEZBIHNjb3Jlcy4KCmBgYHtyfQplel90Ml9mYV8zX3ByZWQkdDFfZmFfc2NvcmVzICU+JQogIG11dGF0ZSh0aW1lID0gInQxX2ZhX3Njb3JlcyIpICU+JQogIHJiaW5kKGV6X3QyX2ZhXzNfcHJlZCR0Ml9wcmVkX3Njb3JlcyAlPiUgbXV0YXRlKHRpbWU9InQyX3ByZWRfc2NvcmVzIikpICU+JQogIGdhdGhlcihmYWN0b3IsIHNjb3JlLCAtc3ViX2lkLCAtdGltZSkgJT4lCiAgc3ByZWFkKHRpbWUsIHNjb3JlKSAlPiUKICBnZ3Bsb3QoYWVzKHQxX2ZhX3Njb3JlcywgdDJfcHJlZF9zY29yZXMsIGNvbD1mYWN0b3IpKSsKICBnZW9tX3BvaW50KCkrCiAgZ2VvbV9hYmxpbmUoYWVzKHNsb3BlPTEsIGludGVyY2VwdD0wKSkrCiAgZmFjZXRfd3JhcCh+ZmFjdG9yLCBzY2FsZXM9J2ZyZWUnKSsKICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpCmBgYAoKTm8gcmVsYXRpb25zaGlwISBBcyBhIHJlc3VsdCBJQ0MncyBhcmUgMCB0b28uCgoqKkJVVCoqIGB0ZW5CZXJnZWAgaXMgbm90IHRoZSBvbmx5IHdheSBvZiBjYWxjdWxhdGluZyBmYWN0b3Igc2NvcmVzLiBgcHN5Y2hgIG9mZmVycyBkaWZmZXJlbnQgbWV0aG9kcyBmb3IgY2FsY3VsYXRpbmcgZmFjdG9yIHNjb3JlcyBiYXNlZCBvbiBbR3JpY2UgKDIwMDEpXShodHRwczovL3NjaG9sYXIuZ29vZ2xlLmNvbS9zY2hvbGFyP2hsPWVuJmFzX3NkdD0wJTJDNSZpbnN0PTU3NDY4ODc5NDU5NTIxNzcyMzcmcT1Db21wdXRpbmcrYW5kK2V2YWx1YXRpbmcrZmFjdG9yK3Njb3JlcytncmljZSsyMDAxJmJ0bkc9KS4gSGVyZSBJIGNhbGN1bGF0ZSB0aGUgSUNDJ3Mgb2YgdGhlIHRocmVlIGZhY3RvcnMgdXNpbmcgb3RoZXIgbWV0aG9kcyBvZiBjYWxjdWxhdGluZyB0aGUgc2NvcmVzLgoKUmVnYXJkbGVzcyBvZiB3aGljaCBtZXRob2QgSSB1c2UgSSBnZXQgdGhlIGZvbGxvd2luZyB3YXJuaW5nCgpgYGAKVGhlIGVzdGltYXRlZCB3ZWlnaHRzIGZvciB0aGUgZmFjdG9yIHNjb3JlcyBhcmUgcHJvYmFibHkgaW5jb3JyZWN0LiAgVHJ5IGEgZGlmZmVyZW50IGZhY3RvciBleHRyYWN0aW9uIG1ldGhvZC4KYGBgCgpJIHRyaWVkIGFsbCBmaXZlIGFuZCBvbmx5IHRocmVlIGdhdmUgbWUgZmFjdG9yIHNjb3JlcyBkZXNwaXRlIHRoaXMgd2FybmluZy4gQnV0IHRoZXkgaW1wbHkgZGlmZmVyZW50IHJlbGlhYmlsaXRpZXMuIGBBbmRlcnNvbmAgYW5kIGBIYXJtYW5gIHlpZWxkIGhpZ2ggSUNDJ3MgZm9yIGFsbCB0aHJlZSBmYWN0b3JzIHdoZXJlIGB0ZW5CZXJnZWAgc2F5cyBpdCBzaG91bGQgYmUgMC4KCmBgYHtyIHdhcm5pbmc9RkFMU0UsIG1lc3NhZ2U9RkFMU0V9CnNjb3JlX3R5cGVfaWNjcyA9IHJiaW5kKGdldF9pY2NfZm9yX3Njb3JlX3R5cGUoInRlbkJlcmdlIiksCiAgICAgIGdldF9pY2NfZm9yX3Njb3JlX3R5cGUoIkFuZGVyc29uIiksCiAgICAgIGdldF9pY2NfZm9yX3Njb3JlX3R5cGUoIkhhcm1hbiIpKQoKc2NvcmVfdHlwZV9pY2NzCmBgYAoKYGBge3J9CnNjb3JlX3R5cGVfaWNjcyAlPiUKICBnYXRoZXIoZmFjdG9yLCBpY2MyLjEsIC1zY29yZXMpICU+JQogIGdncGxvdChhZXMoc2NvcmVzLCBpY2MyLjEsIGNvbD1mYWN0b3IpKSsKICBnZW9tX3BvaW50KHNpemU9Mi41KSsKICB4bGFiKCcnKSsKICB0aGVtZShsZWdlbmQudGl0bGU9ZWxlbWVudF9ibGFuaygpKQpgYGAKClBsb3R0aW5nIHRoZSBzY29yZXMgdXNpbmcgYEFuZGVyc29uYCBhcyBhbiBhbHRlcm5hdGl2ZSBleGFtcGxlLiBSZWZsZWN0aW5nIHRoZSBoaWdoIElDQydzIG5vdyBhbGwgZmFjdG9yIHNjb3JlcyBmb3IgYm90aCB0aW1lIHBvaW50cyBhcmUgaGlnaGx5IGNvcnJlbGF0ZWQuCgpgYGB7ciB3YXJuaW5nPUZBTFNFLCBtZXNzYWdlPUZBTFNFfQplel90Ml9mYV8zX3ByZWRfYmFydGxldHQgPSBwcmVkaWN0X3QyX2ZhX3Njb3JlcyhzY29yZXM9IkFuZGVyc29uIikKCmV6X3QyX2ZhXzNfcHJlZF9iYXJ0bGV0dCR0MV9mYV9zY29yZXMgJT4lCiAgbXV0YXRlKHRpbWUgPSAidDFfZmFfc2NvcmVzIikgJT4lCiAgcmJpbmQoZXpfdDJfZmFfM19wcmVkX2JhcnRsZXR0JHQyX3ByZWRfc2NvcmVzICU+JSBtdXRhdGUodGltZT0idDJfcHJlZF9zY29yZXMiKSkgJT4lCiAgZ2F0aGVyKGZhY3Rvciwgc2NvcmUsIC1zdWJfaWQsIC10aW1lKSAlPiUKICBzcHJlYWQodGltZSwgc2NvcmUpICU+JQogIGdncGxvdChhZXModDFfZmFfc2NvcmVzLCB0Ml9wcmVkX3Njb3JlcywgY29sPWZhY3RvcikpKwogIGdlb21fcG9pbnQoKSsKICBnZW9tX2FibGluZShhZXMoc2xvcGU9MSwgaW50ZXJjZXB0PTApKSsKICBmYWNldF93cmFwKH5mYWN0b3IsIHNjYWxlcz0nZnJlZScpKwogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikKYGBgCgpCYXNlZCBvbiB0aGUgd2FybmluZyBpbiBmaXR0aW5nIHRoZSBtb2RlbHMgSSBkdWcgaW50byB0aGUgYHBzeWNoYCBwYWNrYWdlIHRvIGZpbmQgdGhlIGRpZmZlcmVudCB3ZWlnaHQgbWF0cml4IGNhbGN1bGF0aW9ucyBkZXBlbmRpbmcgb24gdGhlIHNjb3JlIG1ldGhvZHM6Cgp3aGVyZSB4IGlzIGRhdGEgYW5kCnIgPC0gY29yKHgsIHVzZSA9ICJwYWlyd2lzZSIpCmYgPC0gbG9hZGluZ3MoZmFjdG9yX21vZGVsKQpQaGkgPC0gZmFjdG9yX21vZGVsJFBoaQoKYFRodXJzdG9uYApgYGAKUyA8LSBmICUqJSBQaGkKdyA8LSB0cnkoc29sdmUociwgUyksIHNpbGVudCA9IFRSVUUpCmBgYAoKYHRlbkJlcmdlYApgYGAKTCA8LSBmICUqJSBtYXRTcXJ0KFBoaSkKci41IDwtIGludk1hdFNxcnQocikKciA8LSBjb3Iuc21vb3RoKHIpCmludi5yIDwtIHRyeShzb2x2ZShyKSwgc2lsZW50ID0gVFJVRSkKQyA8LSByLjUgJSolIEwgJSolIGludk1hdFNxcnQodChMKSAlKiUgaW52LnIgJSolIEwpCncgPC0gci41ICUqJSBDICUqJSBtYXRTcXJ0KFBoaSkKYGBgCgpgQW5kZXJzb25gCmBgYApJIDwtIGRpYWcoMSwgbmYsIG5mKQpoMiA8LSBkaWFnKGYgJSolIFBoaSAlKiUgdChmKSkKVTIgPC0gMSAtIGgyCmludi5VMiA8LSBkaWFnKDEvVTIpCncgPC0gaW52LlUyICUqJSBmICUqJSBpbnZNYXRTcXJ0KHQoZikgJSolIGludi5VMiAlKiUgciAlKiUgaW52LlUyICUqJSBmKQpgYGAKCmBCYXJ0bGV0dGAKYGBgCkkgPC0gZGlhZygxLCBuZiwgbmYpCmgyIDwtIGRpYWcoZiAlKiUgUGhpICUqJSB0KGYpKQpVMiA8LSAxIC0gaDIKaW52LlUyIDwtIGRpYWcoMS9VMikKdyA8LSBpbnYuVTIgJSolIGYgJSolIChzb2x2ZSh0KGYpICUqJSBpbnYuVTIgJSolIGYpKQpgYGAKCmBIYXJtYW5gCmBgYAptIDwtIGYgJSolIHQoUykKZGlhZyhtKSA8LSAxCmludi5tIDwtIHNvbHZlKG0pCncgPC0gaW52Lm0gJSolIGYKYGBgCgpJJ20gc3R1Y2sgaGVyZSB0aG91Z2ggYW5kIGNhbid0IGZpZ3VyZSBvdXQgd2hhdCB0aGUgcHJvYmxlbSB3aXRoIHRoZSBgdGVuQmVyZ2VgIHdlaWdodCBtYXRyaXggaXMgb3Igd2hhdCB0byBtYWtlIG9mIGRpZmZlcmVudCByZWxpYWJpbGl0eSByZXN1bHRzIGRlcGVuZGluZyBvbiB0aGUgc2NvcmluZyBtZXRob2QuCgojIyMgRml0IGEgMyBmYWN0b3IgbW9kZWwgc2VwYXJhdGVseSBmb3IgVDIgZGF0YS4KCkluc3RlYWQgb2YgcHJlZGljdGluZyBUMiBkYXRhIGZyb20gdGhlIFQxIGZhY3RvciBzb2x1dGlvbiB3ZSBjYW4gYWxzbyBmaXQgYSAzIGZhY3RvciBtb2RlbCB0byB0aGUgVDIgZGF0YSBpbmRlcGVuZGVudGx5LgoKQ29uY2VwdHVhbGx5IHRoaXMgbWlnaHQgYmUgc2ltaWxhciB0byBmaXR0aW5nIHRoZSBERE0ncyBzZXBhcmF0ZWx5IGZvciB0d28gdGltZSBwb2ludHMgd2hlbiBnZW5lcmF0aW5nIHBhcmFtZXRlciBlc3RpbWF0ZXMgaW5zdGVhZCBvZiB1c2luZyB0aGUgZXN0aW1hdGVzIGZyb20gdGhlIGZpcnN0IHRpbWUgcG9pbnQgZm9yIGVhY2ggc3ViamVjdC4KCmBgYHtyfQplel90Ml9mYV8zID0gZmEocmVzX2NsZWFuX3JldGVzdF9kYXRhX2V6LCAzLCByb3RhdGU9J29ibGltaW4nLCBmbT0nbWlucmVzJywgc2NvcmVzPSd0ZW5CZXJnZScpCmBgYAoKUGxvdCBvZiBpbmRlcGVuZGVudCAzIGZhY3RvciBzb2x1dGlvbiBmb3IgVDIgZGF0YSBsb29rcyB2ZXJ5IHNpbWlsYXIgdG8gdGhhdCBvZiBUMSBzb2x1dGlvbiAod2l0aCB0d28gZmFjdG9ycyBzd2l0Y2hlZCBuYW1lcykKCmBgYHtyIG1lc3NhZ2U9RkFMU0V9CmV6X3QyX2ZhXzNfbG9hZGluZ3MgPSBhcy5kYXRhLmZyYW1lKGV6X3QyX2ZhXzMkbG9hZGluZ3NbXSkKCmV6X3QyX2ZhXzNfbG9hZGluZ3NbYWJzKGV6X3QyX2ZhXzNfbG9hZGluZ3MpPDAuM109TkEKCnRtcCA9IGV6X3QyX2ZhXzNfbG9hZGluZ3MgJT4lCiAgbXV0YXRlKGR2ID0gcm93Lm5hbWVzKC4pKSAlPiUKICBzZWxlY3QoZHYsIE1SMSwgTVIyLCBNUjMpICU+JQogIG11dGF0ZShudW1fbG9hZGluZyA9IDMtKGlzLm5hKE1SMSkraXMubmEoTVIyKStpcy5uYShNUjMpICkgKSAlPiUKICBmaWx0ZXIobnVtX2xvYWRpbmchPTApICU+JQogIHNlbGVjdCgtbnVtX2xvYWRpbmcpICU+JQogIGFycmFuZ2UoLU1SMSwgLU1SMiwgLU1SMykgJT4lCiAgbXV0YXRlKG9yZGVyX251bSA9IDE6bigpLAogICAgICAgICBkdiA9IHJlb3JkZXIoZHYsIC1vcmRlcl9udW0pKSAlPiUKICBzZWxlY3QoLW9yZGVyX251bSkgJT4lCiAgZ2F0aGVyKEZhY3RvciwgTG9hZGluZywgLWR2KSAlPiUKICBuYS5leGNsdWRlKCkgJT4lCiAgbXV0YXRlKG5lZ19sb2FkID0gZmFjdG9yKGlmZWxzZShMb2FkaW5nPjAsIk5BIiwiIzAwMDAwMCIpKSwKICAgICAgICAgdmFyX3R5cGUgPSBpZmVsc2UoZ3JlcGwoImRyaWZ0IiwgZHYpLCAiZHJpZiByYXRlIiwKICAgICAgICAgICAgICAgICAgaWZlbHNlKGdyZXBsKCJ0aHJlc2giLCBkdiksICJ0aHJlc2hvbGQiLAogICAgICAgICAgICAgICAgICAgICAgICAgaWZlbHNlKGdyZXBsKCJub25fZGVjIiwgZHYpLCAibm9uLWRlY2lzaW9uIiwgTkEpKSkpCgoKcCA9IHRtcCU+JQogIGdncGxvdChhZXMoZHYsIGFicyhMb2FkaW5nKSwgZmlsbD12YXJfdHlwZSwgY29sID0gbmVnX2xvYWQpKSsKICBnZW9tX2JhcihzdGF0ID0gImlkZW50aXR5IiwgYWxwaGE9MC41KSsKICBmYWNldF93cmFwKH5GYWN0b3IsIG5yb3c9MSkrCiAgY29vcmRfZmxpcCgpKwogIHhsYWIoIiIpKwogIHNjYWxlX2NvbG9yX2lkZW50aXR5KCkrCiAgeWxhYigiQWJzb2x1dGUgTG9hZGluZyIpKwogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJib3R0b20iLAogICAgICAgIGxlZ2VuZC50aXRsZT1lbGVtZW50X2JsYW5rKCkpCgpnZ3NhdmUoJ0VaX0ZBX1QyXzNfd19sYWJlbHMuanBlZycscGxvdD1wLCBkZXZpY2UgPSAnanBlZycsIHBhdGggPSBmaWdfcGF0aCwgd2lkdGggPSAxNSwgaGVpZ2h0ID0gMTUsIHVuaXRzID0gImluIikKCnAgPSB0bXAlPiUKICBnZ3Bsb3QoYWVzKGR2LCBhYnMoTG9hZGluZyksIGZpbGw9dmFyX3R5cGUsIGNvbCA9IG5lZ19sb2FkKSkrCiAgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIsIGFscGhhPTAuNSkrCiAgZmFjZXRfd3JhcCh+RmFjdG9yLCBucm93PTEpKwogIGNvb3JkX2ZsaXAoKSsKICB4bGFiKCIiKSsKICBzY2FsZV9jb2xvcl9pZGVudGl0eSgpKwogIHlsYWIoIkFic29sdXRlIExvYWRpbmciKSsKICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAiYm90dG9tIiwKICAgICAgICBsZWdlbmQudGl0bGU9ZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGV4dC55ID0gZWxlbWVudF9ibGFuaygpKQoKZ2dzYXZlKCdFWl9GQV9UMl8zLmpwZWcnLHBsb3Q9cCwgZGV2aWNlID0gJ2pwZWcnLCBwYXRoID0gZmlnX3BhdGgsIHdpZHRoID0gMTAsIGhlaWdodCA9IDEyLCB1bml0cyA9ICJpbiIpCgpwCmBgYAoKVDFfZml0IHNjb3JlcyB2cyBUMl9maXQgc2NvcmVzCgpgYGB7cn0KZXpfdDJfZmFfM19zY29yZXMgPSBkYXRhLmZyYW1lKGV6X3QyX2ZhXzMkc2NvcmVzKQplel90Ml9mYV8zX3Njb3JlcyRzdWJfaWQgPSByZXRlc3RfZGF0YSRzdWJfaWQKCmRhdGEuZnJhbWUoZXpfdDFfZmFfMyRzY29yZXMpICU+JQogIG11dGF0ZShzdWJfaWQgPSB0ZXN0X2RhdGFfNTIyJHN1Yl9pZCkgJT4lCiAgbGVmdF9qb2luKGV6X3QyX2ZhXzNfc2NvcmVzLCBieSA9ICJzdWJfaWQiKSAlPiUKICBnYXRoZXIoa2V5LCB2YWx1ZSwgLXN1Yl9pZCkgJT4lCiAgc2VwYXJhdGUoa2V5LCBjKCJwYXIiLCAibW9kZWwiKSwgc2VwID0gIlxcLiIpICU+JQogIG11dGF0ZShtb2RlbCA9IGlmZWxzZShtb2RlbD09InkiLCAiVDJfZml0IiwiVDFfZml0IikpICU+JQogIHNwcmVhZChtb2RlbCwgdmFsdWUpJT4lCiAgZ2dwbG90KGFlcyhUMV9maXQsIFQyX2ZpdCkpKwogIGdlb21fcG9pbnQoKSsKICBmYWNldF93cmFwKH5wYXIsIHNjYWxlcz0gImZyZWUiKSsKICBnZW9tX2FibGluZShhZXMoc2xvcGU9MSwgaW50ZXJjZXB0PTApKQpgYGAKClQxX2ZpdCB2cyBUMl9maXQgbG9hZGluZ3MKCmBgYHtyfQplel90MV9mYV8zX2xvYWRpbmdzID0gZGF0YS5mcmFtZShlel90MV9mYV8zJGxvYWRpbmdzW10pICU+JQogIHJlbmFtZShkcmlmdF9yYXRlcyA9IE1SMSwgdGhyZXNob2xkcyA9IE1SMiwgbm9uX2RlY3MgPSBNUjMpICU+JQogIG11dGF0ZShkdiA9IHJvdy5uYW1lcyguKSkKCmV6X3QyX2ZhXzNfbG9hZGluZ3MgPSBkYXRhLmZyYW1lKGV6X3QyX2ZhXzMkbG9hZGluZ3NbXSkgJT4lCiAgcmVuYW1lKGRyaWZ0X3JhdGVzID0gTVIxLCB0aHJlc2hvbGRzID0gTVIzLCBub25fZGVjcyA9IE1SMiklPiUKICBtdXRhdGUoZHYgPSByb3cubmFtZXMoLikpCgplel90MV9mYV8zX2xvYWRpbmdzICU+JQogIGxlZnRfam9pbihlel90Ml9mYV8zX2xvYWRpbmdzLCBieSA9ICJkdiIpICU+JQogIGdhdGhlcihrZXksIHZhbHVlLCAtZHYpICU+JQogIHNlcGFyYXRlKGtleSwgYygicGFyIiwgIm1vZGVsIiksIHNlcCA9ICJcXC4iKSAlPiUKICBtdXRhdGUobW9kZWwgPSBpZmVsc2UobW9kZWwgPT0gIngiLCAiVDFfZml0IiwgIlQyX2ZpdCIpKSU+JQogIHNwcmVhZChtb2RlbCwgdmFsdWUpICU+JQogIGdncGxvdChhZXMoVDFfZml0LCBUMl9maXQpKSsKICBnZW9tX3BvaW50KCkrCiAgZ2VvbV9hYmxpbmUoYWVzKHNsb3BlPTEsIGludGVyY2VwdD0wKSkrCiAgZmFjZXRfd3JhcCh+cGFyKQpgYGAKCiMjIyBNb2RlbCB0aW1lIGFzIGEgbGF0ZW50IHZhcmlhYmxlCgpEYXZlJ3MgcmVzcG9uc2UgdG8gbXkgY29uZnVzaW9ucyBvbiB0aGlzOgoKPlRoZSBzdGFuZGFyZCB3YXkgdG8gZXZhbHVhdGUgdGVzdC1yZXRlc3QgY29ycmVsYXRpb25zIHdpdGggbGF0ZW50IHZhcmlhYmxlcyBpcyB0byBmaXQgYSBsYXRlbnQgdmFyaWFibGUgZm9yIGVhY2ggdGltZSBhbmQgdGhlbiBhbGxvdyB0aGUgbGF0ZW50IHZhcmlhYmxlcyB0byBjb3JyZWxhdGUuIFRoYXQgaXMgdGhlIHRlc3QtcmV0ZXN0IGNvcnJlbGF0aW9uIGZvciB0aGUgYmVzdCwg4oCcdHJ1ZeKAnSBzY29yZSBtZWFzdXJlIGF0IGVhY2ggdGltZSBwb2ludC4KCkJhc2VkIG9uIHRoaXMgSSB0cmllZCBtb2RlbGluZyBhbGwgdGhlIGRhdGEgdG9nZXRoZXIgdXNpbmcgdGltZXBvaW50IGl0IHdhcyBjb2xsZWN0ZWQgaW4gYXMgYSBsYXRlbnQgdmFyaWFibGUuIFRoZSBkZXRhaWxzIG9mIHRob3NlIGFuYWx5c2VzIGFyZSBbaGVyZV0oaHR0cHM6Ly96ZW5rYXZpLmdpdGh1Yi5pby9TUk9fRERNX0FuYWx5c2VzL291dHB1dC9yZXBvcnRzL0xhdGVudFZhcmlhYmxlUmVsaWFiaWxpdHkubmIuaHRtbCkKCiMjIEVGQSBvbiBIRERNIHZhcmlhYmxlcyBvZiByZXRlc3QgZGF0YQoKYGBge3J9CnJldGVzdF9kYXRhX2hkZG0gPSByZXRlc3RfZGF0YSAlPiUKICBzZWxlY3QoZ3JlcCgnaGRkbScsIG5hbWVzKHJldGVzdF9kYXRhKSwgdmFsdWU9VCkpCmBgYAoKUmVtb3ZlIHZhcmlhYmxlcyB0aGF0IGFyZSBjb3JyZWxhdGVkID4wLjg1CgpgYGB7cn0KY2xlYW5fcmV0ZXN0X2RhdGFfaGRkbSA9IHJlbW92ZV9jb3JyZWxhdGVkX3Rhc2tfdmFyaWFibGVzKHJldGVzdF9kYXRhX2hkZG0pCmBgYAoKUmVtb3ZlIG91dGxpZXJzICg+Mi41IFNEIGF3YXkpCgpgYGB7cn0KY2xlYW5fcmV0ZXN0X2RhdGFfaGRkbSA9IGFzLmRhdGEuZnJhbWUoYXBwbHkoY2xlYW5fcmV0ZXN0X2RhdGFfaGRkbSwgMiwgcmVtb3ZlX291dGxpZXJzKSkKYGBgCgpUcmFuc2Zvcm0gc2tld2VkIHZhcmlhYmxlcyBhcyBpbiBUMQoKYGBge3J9CmNsZWFuX3JldGVzdF9kYXRhX2hkZG0gPSBjbGVhbl9yZXRlc3RfZGF0YV9oZGRtICU+JQogIG11dGF0ZShkb3RfcGF0dGVybl9leHBlY3RhbmN5LmhkZG1fdGhyZXNoLmxvZ1RyID0gbG9nKGRvdF9wYXR0ZXJuX2V4cGVjdGFuY3kuaGRkbV90aHJlc2gpLApzdGltX3NlbGVjdGl2ZV9zdG9wX3NpZ25hbC5oZGRtX3RocmVzaC5sb2dUciA9IGxvZyhzdGltX3NlbGVjdGl2ZV9zdG9wX3NpZ25hbC5oZGRtX3RocmVzaCkpICU+JQogIHNlbGVjdCgtZG90X3BhdHRlcm5fZXhwZWN0YW5jeS5oZGRtX3RocmVzaCwtc3RpbV9zZWxlY3RpdmVfc3RvcF9zaWduYWwuaGRkbV90aHJlc2gpCmBgYAoKRHJvcCBzdWJqZWN0IGlkZW50aWZpZXIgY29sdW1uLCBtZWFuIGltcHV0ZSBhbmQgZHJvcCBjb2xzIHdpdGggbm8gdmFyaWFuY2UKCmBgYHtyfQpjbGVhbl9yZXRlc3RfZGF0YV9oZGRtX3N0ZCA9IGNsZWFuX3JldGVzdF9kYXRhX2hkZG0gJT4lIG11dGF0ZV9pZihpcy5udW1lcmljLCBzY2FsZSkKCiNtZWFuIGltcHV0YXRpb24KY2xlYW5fcmV0ZXN0X2RhdGFfaGRkbV9zdGRbaXMubmEoY2xlYW5fcmV0ZXN0X2RhdGFfaGRkbV9zdGQpXT0wCgojZHJvcCBjb2xzIHdpdGggbm8gdmFyaWFuY2UKY2xlYW5fcmV0ZXN0X2RhdGFfaGRkbV9zdGQgPSBjbGVhbl9yZXRlc3RfZGF0YV9oZGRtX3N0ZCAlPiUKICBzZWxlY3RfaWYoZnVuY3Rpb24oY29sKSBzZChjb2wpICE9IDApCmBgYAoKUmVzaWR1YWxpemUgQWdlIGFuZCBTZXggZWZmZWN0cwoKYGBge3Igd2FybmluZz1GQUxTRSwgbWVzc2FnZT1GQUxTRX0KY2xlYW5fcmV0ZXN0X2RhdGFfaGRkbV9zdGQgPSBjYmluZChjbGVhbl9yZXRlc3RfZGF0YV9oZGRtX3N0ZCwgZGVtb2dyYXBoaWNzWyxjKCJBZ2UiLCAiU2V4IildKQoKcmVzX2NsZWFuX3JldGVzdF9kYXRhX2hkZG0gPSByZXNpZHVhbGl6ZV9iYXNlbGluZShjbGVhbl9yZXRlc3RfZGF0YV9oZGRtX3N0ZCkKYGBgCgpTZWxlY3Qgb25seSB0aGUgdmFycyB0aGF0IHdlbnQgaW4gdG8gdGhlIGxpbWl0ZWQgc2V0IG9mIGhkZG0gdmFyaWFibGVzCgpgYGB7cn0KcmVzX2NsZWFuX3JldGVzdF9kYXRhX2hkZG1fc3Vic2V0ID0gcmVzX2NsZWFuX3JldGVzdF9kYXRhX2hkZG0gJT4lCiAgc2VsZWN0KGhkZG1fdmFyX3N1YnNldCkKYGBgCgpQcmVkaWN0IHVzaW5nIHRoZSAzIGZhY3RvciBFRkEgZnJvbSBUMQoKYGBge3J9CmhkZG1fdDJfZmFfM19zdWJzZXQgPSBwcmVkaWN0KGhkZG1fdDFfZmFfM19zdWJzZXQsIHJlc19jbGVhbl9yZXRlc3RfZGF0YV9oZGRtX3N1YnNldCkKYGBgCg==